将实现特征的数据存储在向量中

Store data that implements a trait in a vector

总的来说,我对 Rust 和系统语言还很陌生。我目前正在研究 Rust 来探索这门语言。我有一个我无法自己解决的问题。而且我想我已经了解发生了什么问题。

我不想在向量中存储实现 trait BaseStuff 的对象。在 Rust 中对我来说不是一个简单的任务 :-).

这是我无法编译的示例代码。

trait BaseStuff {}

struct MyStuff {
    value: i32,
}

struct AwesomeStuff {
    value: f32,
    text: String,
}

impl BaseStuff for MyStuff {}

impl BaseStuff for AwesomeStuff {}

struct Holder {
    stuff: Vec<BaseStuff>,
}

impl Holder {
    fn register(&mut self, data: impl BaseStuff) {
        self.stuff.push(data);
    }
}

fn main() {
    let my_stuff = MyStuff { value: 100 };

    let awesome_stuff = AwesomeStuff {
        value: 100.0,
        text: String::from("I'm so awesome!"),
    };

    let mut holder = Holder { stuff: vec![] };

    holder.register(my_stuff);
}

error[E0277]: the size for values of type (dyn BaseStuff + 'static) cannot be known at compilation time --> src\main.rs:17:5 | 17 |
stuff: Vec, // unknown size | ^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time | = help: the trait std::marker::Sized is not implemented for (dyn BaseStuff + 'static) = note: to learn more, visit https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait = note: required by std::vec::Vec

error: aborting due to previous error

For more information about this error, try rustc --explain E0277. error: Could not compile playground.

The compiler message is clear and I understand the message. I can implement the trait BaseStuff in any struct I want't so its unclear which size it is. Btw the link isn't helpful because its pointing to outdated site...

String 的大小也是未知的,但是 String 实现了特征 std::marker::Sized,这就是为什么 Vec<String> 可以正常工作的原因。对吗?

在我读到的关于大小未知的数据类型的 rust 书中,我必须将这些数据存储在堆上而不是堆栈上。我更改了我的代码如下。

struct Holder {
    stuff: Vec<Box<BaseStuff>>,
}

impl Holder {
    fn register(&mut self, data: impl BaseStuff) {
        self.stuff.push(Box::new(data));
    }
}

现在我遇到了一个新的编译器问题:

error[E0310]: the parameter type impl BaseStuff may not live long enough --> src\main.rs:22:25 | 21 | fn register(&mut self, data: impl BaseStuff) { |
--------------- help: consider adding an explicit lifetime bound impl BaseStuff: 'static... 22 | self.stuff.push(Box::new(data));
| ^^^^^^^^^^^^^^ | note: ...so that the type impl BaseStuff will meet its required lifetime bounds --> src\main.rs:22:25 | 22 | self.stuff.push(Box::new(data));
| ^^^^^^^^^^^^^^

error: aborting due to previous error

For more information about this error, try rustc --explain E0310. error: Could not compile playground.

知道我出局了……我读了关于生命周期的书,并用 'a 在这里和 'a 任意组合更改了我的代码,但没有运气……我不想写下我尝试过的任何生命周期定义组合。 我不明白为什么我需要生命周期定义。所有权在任何步骤中都会移动,因此据我了解,很明显 Holder 结构是所有数据的所有者。是吗?

如何更正我的代码以进行编译?

感谢您的帮助。

几乎明白了——这里的问题是实现BaseStuff的类型可能是一个引用(例如impl BaseStuff for &SomeType)。这意味着即使您按值传递 data,该值也可能是一个引用,您的 Box.

将不再存在

解决这个问题的方法是添加一个约束,使对象具有 'static 生命周期,这意味着它要么是值类型,要么是静态引用。您可以将此约束应用于特征或接受特征的方法,具体取决于您的用例。

将约束应用于特征:

trait BaseStuff: 'static {}

将约束应用于方法:

impl Holder {
    fn register(&mut self, data: impl BaseStuff + 'static) {
        self.stuff.push(Box::new(data));
    }
}

如果将 'static 约束添加到方法中,我建议也将其添加到 Vec 中以避免丢失类型信息,如下所示:

struct Holder {
    stuff: Vec<Box<dyn BaseStuff + 'static>>,
}