结构中的特征参考

nei*_*son 57 traits rust

我有一个特点 Foo

pub trait Foo {
   fn do_something(&self) -> f64;
}
Run Code Online (Sandbox Code Playgroud)

以及引用该特征的结构

pub struct Bar {
   foo: Foo,
}
Run Code Online (Sandbox Code Playgroud)

我试着编译

error: reference to trait `Foo` where a type is expected; try `Box<Foo>` or `&Foo`
Run Code Online (Sandbox Code Playgroud)

将结构更改为

struct Bar {
   foo: &Foo,
}
Run Code Online (Sandbox Code Playgroud)

告诉我 error: missing lifetime specifier

将定义更改为

struct Bar {
   foo: Box<Foo>,
}
Run Code Online (Sandbox Code Playgroud)

编译 - 耶!

但是,当我想要一个函数返回foobar- 类似于:

impl Bar {
    fn get_foo(&self) -> Foo {
        self.foo
    }
}
Run Code Online (Sandbox Code Playgroud)

显然bar.foo是一个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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

改为

impl Bar {
    fn get_foo(self) -> Box<Foo> {
        self.foo
    }
}
Run Code Online (Sandbox Code Playgroud)

一切都好.

所以....

  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() {}
Run Code Online (Sandbox Code Playgroud)

Lev*_*ans 103

这是trait对象的棘手问题,你需要非常清楚谁拥有底层对象.

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

随着参考

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

struct Bar {
   foo : &Foo,
}
Run Code Online (Sandbox Code Playgroud)

问题是,引用不拥有底层对象,而另一个对象或范围必须在某处拥有它:您只是借用它.因此,编译器需要有关此引用有效期的信息:如果底层对象被销毁,则Bar实例将引用已释放的内存,这是禁止的!

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

struct Bar<'a> {
   foo : &'a (Foo + 'a),
}
Run Code Online (Sandbox Code Playgroud)

你在这里对编译器说的是:"我的Bar对象不能比它里面的Foo引用更长".您必须指定生命周期两次:一次用于引用的生命周期,一次用于trait对象本身,因为traits可以用于引用,如果底层对象是引用,则还必须指定其生命周期.

在特殊情况下将写作:

struct Bar<'a> {
   foo : &'a (Foo + 'static),
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,'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);
}
Run Code Online (Sandbox Code Playgroud)

随着盒子

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

struct Bar<'a> {
    foo: Box<Foo + 'a>
}
Run Code Online (Sandbox Code Playgroud)

如果你知道底层对象不能作为参考,你也可以写:

struct Bar {
    foo: Box<Foo + 'static>
}
Run Code Online (Sandbox Code Playgroud)

而终身问题完全消失了.

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

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>);
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,'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();
}
Run Code Online (Sandbox Code Playgroud)

随着光值

回答你的上一个问题:

是什么意思去除&只是使用自我?

如果方法有这样的定义:

fn unwrap(self) {}
Run Code Online (Sandbox Code Playgroud)

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

这是一个通常用于回馈结构所拥有的数据所有权的过程.您将unwrap()在标准库中遇到许多功能.

  • 从那时起,生命周期限制似乎发生了变化。我现在可以写 `foo: &amp;'a Foo` 和 `foo: Box&lt;Foo&gt;`。什么解释了这些变化? (3认同)

Ari*_*lec 14

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

struct Bar<'a> {
    foo: &'a Foo + 'a,
}
Run Code Online (Sandbox Code Playgroud)

struct Bar<'a> {
    foo: &'a (Foo + 'a), // with parens
}
Run Code Online (Sandbox Code Playgroud)

根据RFC 438

  • 我认为这*再次*过时了:)现在你还需要摆弄一些“dyn”,所以我的编译器告诉我。 (2认同)

Yur*_*rik 13

在 2021 年版本中,dyn现在需要Trait对象。从 1.57 开始,语法box仍然不稳定,因此应该使用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));
}
Run Code Online (Sandbox Code Playgroud)

  • 不再需要包含生命周期注释,因为 rustc 可以在本示例中暗示它们的存在。 (3认同)