对结构特征的引用


72

我有个特质 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>,
}

编译-是的!

但是,当我希望函数返回时foobar类似:

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

好吧,显然bar.foo是a Box<Foo>,所以我希望得到error: reference to trait `Foo` where a type is expected; try `Box<Foo>` or `&Foo`

将签名更改为

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

但是现在我开始error: cannot move out of dereference of `&`-pointer尝试取消引用self

更改为

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

一切都很好。

所以....

  1. 为什么&bar结构中不起作用?我假设我必须装箱,因为结构体具有设置的内存布局,所以我们不得不说它是一个指向特征的指针(因为我们不知道它的大小),但是为什么编译器会建议一些不会编译的东西?
  2. 为什么我不能间接引用selfget_foo()-所有的例子我见过使用借入self语法?
  3. 删除&和仅使用意味着什么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() {}

Answers:


126

这是特征对象的棘手点,您需要非常明确地了解谁拥有基础对象。

确实,当您使用特征作为类型时,底层对象必须存储在某个位置,因为特征对象实际上是对实现给定特征的对象的引用。这就是为什么不能将裸MyTrait作为类型,它必须是引用&MyTrait或box的原因Box<MyTrait>

带参考

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

struct Bar {
   foo : &Foo,
}

问题是,引用不拥有基础对象,而另一个对象或作用域必须在某处拥有它:您只是在借用它。因此,编译器需要有关此引用有效期的信息:如果基础对象被破坏,则您的Bar实例将具有对释放的内存的引用,这是禁止的!

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

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

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

在特殊情况下将编写:

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

在这种情况下,'static要求底层对象必须是真实的结构或&'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()在标准库中遇到很多功能。


哇。感谢您提供这么多的细节。在接受之前,我会先阅读一遍,但这给了我很多乐趣,并尝试去理解。
尼尔丹森(Neil danson),2014年

感谢您的详细说明!-这是来自基于班级语言的背景的陡峭学习曲线。
罗伯·奈特

2
从那时起,生命周期限制似乎已经改变。我现在可以写foo: &'a Foofoo: Box<Foo>。是什么解释了这些变化?
nhaarman'4-4-5

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.