rust 中的独立类型指针数组

Posted

技术标签:

【中文标题】rust 中的独立类型指针数组【英文标题】:Array of independent typed pointers in rust 【发布时间】:2021-10-07 02:55:06 【问题描述】:

我有一个使用类型参数的自定义结构,例如:

struct Foo<'a, T: ?Sized> 
    parser: &'a dyn Fn(&str) -> Box<T>,
    value: Option<Box<T>>

正如T: ?Sized 所示,Foo 类型元素的大小不会随T 的大小而变化(感谢Boxes)。

我想将多个Foo 元素放入一个数组中,这些元素可以有不同的T,并将其传递给一个函数。

为此,我尝试了一些类似的方法:

fn parse_all<'a, T>(args: &mut [&mut Foo<'a, T>]) where T: Any;

fn main() 
    let a: Foo<i32> = Fooparser = ..., value = None;
    let b: Foo<String> = Fooparser = ..., value = None;
    parse_all(&mut[&mut a, &mut b]);

当然会失败,因为 ab 有不同的 T 类型。

这个想法是我不需要知道数组元素的确切类型,因为Foo::parser 函数指针会处理它们。此外,它们的大小都是恒定的,所以我不应该有大小问题。

我是否可以通过使用Foo 绕过数组的每个元素具有完全相同且具有不同实际类型的ab 的事实? (例如在 C 中,我们可以将 void* 转换为任何指针类型)

顺便说一下,数组可以是任意大小,所以据我所知,我不能使用元组。

【问题讨论】:

请注意,您对Foo&lt;T&gt; 大小的评论不完整。类型不会随 T 的 size 变化,但仍会随 T 的 type 而变化,例如 Foo&lt;String&gt;Foo&lt;dyn std::fmt::Display&gt;。 play.rust-lang.org/… 【参考方案1】:

创建数组的问题不在于每个Foo&lt;T&gt; 会有不同的大小 - 正如您指出的那样,它们将是相同的。然而,每一个都可能有不同的 type,这是需要以某种方式存储的额外信息,Rust 不支持数组。

由于您似乎并不关心解析过程中生成的类型是什么,而不是Foo 中的泛型参数,您可以使用Box&lt;dyn Any&gt;,然后仅在提取结果时向下转换:

struct Foo<'a> 
    parser: &'a dyn Fn(&str) -> Box<dyn Any>,
    value: Option<Box<dyn Any>>,


impl Foo<'_> 
    fn try_extract<T: 'static>(&mut self) -> Option<Box<T>> 
        if let Some(val) = self.value.take() 
            match val.downcast::<T>() 
                Ok(x) => Some(x),
                Err(x) => 
                    self.value = Some(x);
                    None
                
            
         else 
            None
        
    

在您的代码中,您没有将任何字符串传递给 parse_all,但我看不出它可以完成什么,所以我为工作示例稍微修改了它:playground

请注意,如果 T 没有大小,这将不起作用,因为 Box::downcast 需要 T: Sized。 您还需要以某种方式独立于解析器数组存储要解码的类型,但是如果不查看更多代码,很难提出一个好的方法来做到这一点。

【讨论】:

非常感谢您的回答和付出的努力。您对 parse_all 应该做什么假设得很好,我可能删除了太多代码。在我的情况下,T 的大小不应该太小,所以我可以接受。我的Foo 元素通常是手工创建的,因为你帮助我的将是我正在做的一个小参数解析库的 api 的一部分。就我而言,@Frxstrem 解决方案更合适,所以我会改用它,但您的解决方案仍然很棒!【参考方案2】:

数组的所有元素必须属于同一类型。但是,该类型可以是 trait 对象,例如 Box&lt;dyn FooTrait&gt;&amp;mut dyn FooTrait 用于自定义 trait FooTrait

您可以为您的所有Foo 类型定义特征并实现它,并使用一些您可以在不知道具体类型T 的情况下调用的通用接口:

trait FooTrait 
    fn do_something(&mut self);


impl<'a, T> FooTrait for Foo<'a, T> 
    fn do_something(&mut self) 
        unimplemented!()
    

因为&amp;mut T 如果T: FooTrait 自动转换为&amp;mut dyn FooTrait,您可以在函数签名中使用它:

fn parse_all(args: &mut [&mut dyn FooTrait]) 
//                            ^^^^^^^^^^^^
    for arg in args 
        arg.do_something();
    


fn main() 
    let mut a: Foo<i32> = Fooparser = ..., value = None;
    let mut b: Foo<String> = Fooparser = ..., value = None;

    parse_all(&mut[&mut a, &mut b]);

如果所有Foo&lt;T&gt; 的大小都相同,这并不重要,因为只有引用(总是相同的大小)存储在数组中。因此,如果您想避免堆分配,您也可以在 Foo 结构中删除 Box 的使用:

struct Foo<'a, T> 
    parser: &'a dyn Fn(&str) -> T,
    value: Option<T>

【讨论】:

以上是关于rust 中的独立类型指针数组的主要内容,如果未能解决你的问题,请参考以下文章

第32课 数组指针和指针数组分析

C/C++中的指针数组和数组指针

go语言入门四(复合类型 数组切片 指针)

go语言入门四(复合类型 数组切片 指针)

指针数组和数组指针的区别以及main函数

C语言的数据类型——指针