【问题标题】:References to traits in structs对结构中特征的引用
【发布时间】:2023-03-18 12:00:01
【问题描述】:

我有一个特质Foo

pub trait Foo {
   fn do_something(&self) -> f64;
}

以及引用该特征的结构

pub struct Bar {
   foo: Foo,
}

尝试编译我得到

error: reference to trait `Foo` where a type is expected; try `Box<Foo>` or `&Foo`

将结构更改为

struct Bar {
   foo: &Foo,
}

告诉我error: missing lifetime specifier

将定义改为

struct Bar {
   foo: Box<Foo>,
}

编译——耶!

但是,当我想要一个函数在 bar 上返回 foo - 类似于:

impl Bar {
    fn get_foo(&self) -> Foo {
        self.foo
    }
}

很明显bar.fooBox&lt;Foo&gt;,所以预计我会得到error: reference to trait `Foo` where a type is expected; try `Box&lt;Foo&gt;` or `&amp;Foo`

将签名改为

impl Bar {
    fn get_foo(&self) -> Box<Foo> {
        let this = *self;
        this.foo
    }
}

但现在我在尝试取消引用 self 时收到 error: cannot move out of dereference of `&amp;`-pointer

改成

impl Bar {
    fn get_foo(self) -> Box<Foo> {
        self.foo
    }
}

一切都好。

所以....

  1. 为什么&amp;bar 结构中不起作用?我假设我必须装箱 由于结构有一个固定的内存布局,所以我们不得不说它是一个指针 到一个特征(因为我们不知道它会有多大),但为什么 编译器建议一些不会编译的东西?
  2. 为什么我不能在 get_foo() 中取消引用 self - 我看到的所有示例都使用借用的 self 语法?
  3. 删除&amp; 并仅使用self 意味着什么?

学习 Rust 令人着迷,但内存安全性既令人着迷又令人生畏!

可编译的完整代码:

trait Foo {
    fn do_something(&self) -> f64;
}

struct Bar {
    foo: Box<Foo>,
}

impl Bar {
    fn get_foo(self) -> Box<Foo> {
        let foo = self.foo;
        foo.do_something();
        foo
    }
}

fn main() {}

【问题讨论】:

    标签: rust traits


    【解决方案1】:

    这是 trait 对象的棘手点,你需要非常明确地知道谁拥有底层对象。

    确实,当您使用 trait 作为类型时,底层对象必须存储在某个地方,因为 trait 对象实际上是对实现给定 trait 的对象的引用。这就是为什么您不能将裸 MyTrait 作为类型,它必须是引用 &amp;MyTrait 或框 Box&lt;MyTrait&gt;

    有参考文献

    您尝试的第一种方法是使用引用,编译器抱怨缺少生命周期说明符:

    struct Bar {
       foo : &Foo,
    }
    

    问题是,一个引用并不拥有底层对象,而另一个对象或作用域必须在某个地方拥有它:你只是借用它。因此,编译器需要有关此引用有效时间的信息:如果底层对象被销毁,您的 Bar 实例将引用释放的内存,这是被禁止的!

    这里的想法是添加生命周期:

    struct Bar<'a> {
       foo : &'a (Foo + 'a),
    }
    

    您在这里对编译器说的是:“我的 Bar 对象不能比其中的 Foo 引用寿命长”。您必须指定两次生命周期:一次用于引用的生命周期,一次用于 trait 对象本身,因为可以为引用实现特征,并且如果底层对象是引用,则还必须指定其生命周期。

    在特殊情况下会写:

    struct Bar<'a> {
       foo : &'a (Foo + 'static),
    }
    

    在这种情况下,'static 要求底层对象必须是一个真正的结构,或者一个 &amp;'static 引用,但不允许其他引用。

    此外,要构建您的对象,您必须为其提供对您自己存储的其他对象的引用。

    你最终会得到这样的结果:

    trait Foo {}
    
    struct MyFoo;
    
    impl Foo for MyFoo {}
    
    struct Bar<'a> {
        foo: &'a (Foo + 'a),
    }
    
    impl<'a> Bar<'a> {
        fn new(the_foo: &'a Foo) -> Bar<'a> {
            Bar { foo: the_foo }
        }
    
        fn get_foo(&'a self) -> &'a Foo {
            self.foo
        }
    }
    
    fn main() {
        let myfoo = MyFoo;
        let mybar = Bar::new(&myfoo as &Foo);
    }
    

    带盒子

    相反,一个 Box 拥有它的内容,因此它允许您将底层对象的所有权授予您的 Bar 结构。然而,由于这个底层对象可能是一个引用,因此您还需要指定生命周期:

    struct Bar<'a> {
        foo: Box<Foo + 'a>
    }
    

    如果你知道底层对象不能是引用,你也可以这样写:

    struct Bar {
        foo: Box<Foo + 'static>
    }
    

    寿命问题完全消失了。

    对象的构造因此相似,但更简单,因为您不需要自己存储底层对象,它由盒子处理:

    trait Foo {}
    
    struct MyFoo;
    
    impl Foo for MyFoo {}
    
    struct Bar<'a> {
        foo: Box<Foo + 'a>,
    }
    
    impl<'a> Bar<'a> {
        fn new(the_foo: Box<Foo + 'a>) -> Bar<'a> {
            Bar { foo: the_foo }
        }
    
        fn get_foo(&'a self) -> &'a Foo {
            &*self.foo
        }
    }
    
    fn main() {
        let mybar = Bar::new(box MyFoo as Box<Foo>);
    }
    

    在这种情况下,'static 版本将是:

    trait Foo {}
    
    struct MyFoo;
    
    impl Foo for MyFoo {}
    
    struct Bar {
        foo: Box<Foo + 'static>,
    }
    
    impl Bar {
        fn new(the_foo: Box<Foo + 'static>) -> Bar {
            Bar { foo: the_foo }
        }
    
        fn get_foo<'a>(&'a self) -> &'a Foo {
            &*self.foo
        }
    }
    
    fn main() {
        let mybar = Bar::new(box MyFoo as Box<Foo>);
        let x = mybar.get_foo();
    }
    

    用裸值

    回答你的最后一个问题:

    删除 & 只使用 self 的含义是什么?

    如果一个方法有这样的定义:

    fn unwrap(self) {}
    

    这意味着它会在这个过程中消耗你的对象,并且在调用bar.unwrap()之后,你将无法再使用bar

    这是一个通常用于归还您的结构所拥有数据的所有权的过程。您会在标准库中遇到很多unwrap() 函数。

    【讨论】:

    • 哇。谢谢你这么详细。在接受之前,我会仔细阅读这篇文章,但它给了我很多可以尝试和理解的东西。
    • 感谢您的详细描述! - 这是一个陡峭的学习曲线,来自基于类的语言背景。
    • 从那时起,似乎生命周期限制发生了变化。我现在可以写foo: &amp;'a Foofoo: Box&lt;Foo&gt;。是什么解释了这些变化?
    【解决方案2】:

    注意以备将来参考:语法已更改为

    struct Bar<'a> {
        foo: &'a Foo + 'a,
    }
    

    struct Bar<'a> {
        foo: &'a (Foo + 'a), // with parens
    }
    

    RFC 438

    【讨论】:

      【解决方案3】:

      在 2021 版中,dyn 现在是用于 Trait 对象的 requirementbox 语法在 1.57 中仍然不稳定,所以应该使用 Box::new() 代替(但不需要强制转换)。所以我认为@Levans上面的例子应该稍微修改一下:

      trait Foo {}
      
      struct MyFoo;
      
      impl Foo for MyFoo {}
      
      struct Bar<'a> {
          foo: Box<dyn Foo + 'a>,
      }
      
      impl<'a> Bar<'a> {
          fn new(the_foo: Box<dyn Foo + 'a>) -> Bar<'a> {
              Bar { foo: the_foo }
          }
      
          fn get_foo(&'a self) -> &'a dyn Foo {
              &*self.foo
          }
      }
      
      fn main() {
          let mybar = Bar::new(Box::new(MyFoo));
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-24
        • 2019-04-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多