结构字段应该都是相同的特征,但不一定是相同的类型

Posted

技术标签:

【中文标题】结构字段应该都是相同的特征,但不一定是相同的类型【英文标题】:Struct fields should be all of same trait, but not neceesarily same type 【发布时间】:2021-11-06 05:49:30 【问题描述】:

我正在尝试实现以下特征和结构:

pub trait Funct 
    fn arity(&self) -> u32;


#[derive(Debug, Hash, Eq, PartialEq, Clone)]
pub struct FunctionLiteral<T: Funct> 
    pub function: T,
    pub args: Vec< FunctionLiteral<T> >


pub enum Foo 
    Foo


impl Funct for Foo 
    fn arity(&self) -> u32 0


pub enum Bar 
    Bar


impl Funct for Bar 
    fn arity(&self) -> u32 0


fn main() 
    let baz = FunctionLiteral
        function: Foo::Foo,
        args: vec![FunctionLiteral
            function: Bar::Bar, 
            args: vec![]
        ]
    ;

我可以按照我对泛型类型 T 的方式进行设置,使其具有特征 Funct,但我不一定希望 T 具有相同的类型。

这里,编译代码会出现以下错误:

error[E0308]: mismatched types
  --> foo.rs:31:23
   |
31 |             function: Bar::Bar, 
   |                       ^^^^^^^^ expected enum `Foo`, found enum `Bar`

error: aborting due to previous error

是否可以设置FunctionLiteral 以便我可以为functionargs 的项目设置不同的类型,同时强制它们都为Funct 类型?

【问题讨论】:

是的,你应该Box他们 Box&lt;dyn Funct&gt; 特别是。那么FunctionLiteral 甚至不需要是通用的。见doc.rust-lang.org/book/ch17-02-trait-objects.html 【参考方案1】:

问题

当你这样做时:

Structure<T: Trait>
    inner: T,
    many: Vec<T>

您告诉编译器为每个不同的T 创建一个专用实例。所以如果你有FooBar都实现了Trait,那么编译器会生成两种不同的表示,有两种不同的大小:

struct Foo(u8);
impl Trait for Foo
    // impl goes here


struct Bar(u64);
impl Trait for Bar
    // impl goes here

然后编译器会生成类似的东西:

Structure<Foo>
    inner: Foo,
    many: Vec<Foo>


// and 
Structure<Bar>  
    inner: Bar, 
    many: Vec<Bar>

显然您不能将 Foo 实例放入 Bar,因为它们是不同的类型并且具有不同的大小。

解决办法

您需要 Box&lt;&gt; 您的 Funct 类型以使它们具有相同的大小(即指针大小)。通过将它们放在(智能)指针后面,您实际上是在擦除它们的类型:

    let a: Box<dyn Trait> = Box::new(Foo(0));
    let b: Box<dyn Trait> = Box::new(Bar(0));

现在ab 具有相同的大小(指针的大小)并且具有相同的类型 - Box&lt;dyn Trait&gt;。所以现在你可以这样做了:

struct Structure // no longer generic!
    inner: Box<dyn Trait>, // can hold any `Box<dyn Trait>`
    many: Vec<Box<dyn Trait>> // can hold any `Box<dyn Trait>`

这种方法的缺点是它需要堆分配并且它丢失了ab 的确切类型。你不再知道aFoo 还是Bar 还是别的什么。

如果您需要它的功能,您可以使用任何其他智能指针代替Box,例如RcArc

另一种选择

另一种选择是使FooBar 具有相同的大小和类型。这可以通过将它们包装在 enum 中来完成:

enum Holder
    Foo(Foo),
    Bar(Bar),  // any other types go here in their own variants

那么你的结构将如下所示:

struct Structure // no longer generic!
    inner: Holder, // can hold any Holder variant`
    many: Vec<Holder> // can hold any Holder variant`

缺点是您必须实现如下委托:

impl Trait for Holder
    fn some_method(&self)
        match self
            Holder::Foo(foo) => foo.some_method(),
            Holder::Bar(bar) => bar.some_method(),
        
    

match 在您想使用该对象的任何地方。现在你的Holder 枚举的大小也将是max(sizeof(Foo), sizeof(Bar))

有利的一面:

你仍然知道实际的类型 - 它没有被删除 没有堆分配

【讨论】:

以上是关于结构字段应该都是相同的特征,但不一定是相同的类型的主要内容,如果未能解决你的问题,请参考以下文章

golang基础数据结构

如何将结构转换为具有完全相同字段名称和类型的另一个结构?

如何对比两个相同数据库表的不同

新老数据库怎么进行数据迁移都是mysql版本相同但是表结构不同

Go语言基础之切片

数据结构--线性表