如何指定结构体字段必须实现特征?

问题描述 投票:0回答:3

我有一个特点

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>,
}

编译 - 耶!

但是,当我想要一个函数在

foo
上返回
bar
时 - 类似于:

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

很明显,

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
    }
}

但现在我在尝试取消引用时得到了

error: cannot move out of dereference of `&`-pointer
self

更改为

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

一切都好。

所以....

  1. 为什么
    &
    结构中的
    bar
    不起作用?我假设我必须装箱 由于结构体有固定的内存布局,所以我们不得不说它是一个指针 到一个特征(因为我们不知道它有多大),但是为什么 编译器建议一些无法编译的东西?
  2. 为什么我不能在
    self
    中取消引用
    get_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() {}
rust traits
3个回答
166
投票

这是特征对象的棘手点,你需要非常明确谁拥有底层对象。

事实上,当您使用特征作为类型时,底层对象必须存储在某个地方,因为特征对象实际上是对实现给定特征的对象的引用。这就是为什么你不能将裸露的

MyTrait
作为类型,它必须是引用
&MyTrait
或盒子
Box<MyTrait>

有参考资料

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

struct Bar {
   foo : &Foo,
}

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

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

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

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

特殊情况会写:

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()
函数。


22
投票

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

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

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

根据 RFC 438


9
投票

在 2021 年版本中,

dyn
现在是 Trait 对象的 要求。自 1.57 起,
box
语法仍然不稳定,因此应使用
Box::new()
代替(但无需强制转换)。所以我认为@Levans 上面的例子应该稍微修改一下。请注意,也不再需要生命周期。

trait Foo {}

struct MyFoo;

impl Foo for MyFoo {}

struct Bar {
    foo: Box<dyn Foo>,
}

impl Bar {
    fn new(the_foo: Box<dyn Foo>) -> Bar {
        Bar { foo: the_foo }
    }

    fn get_foo(&self) -> &dyn Foo {
        &*self.foo
    }
}

fn main() {
    let mybar = Bar::new(Box::new(MyFoo));
    let myfoo = mybar.get_foo();
    println!("{myfoo:p}")
}
© www.soinside.com 2019 - 2024. All rights reserved.