2
votes

I'm very new to Rust and system languages in general. And I'm currently playing around with Rust to explore the language. I've a problem that I cannot fix by myself. And I think I've understanding problem whats going on.

I wan't to store objects that implements the trait BaseStuff in a vector. In Rust not a simple task for me :-).

Here is my example code that won't compile.

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

The size of String is also unknown, but the String implements the trait std::marker::Sized and that's why a Vec<String> will work without problems. Is that correct?

In the rust book I read for data types with unknown size I've to store these data on the heap instead of the stack. I changed my code as follows.

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

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

Now I'm hitting a new compiler issue:

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.

And know I'm out... I read in the book about lifetimes and changed my code a lot with 'a here and 'a in any combination but without luck... I don't want to write down any lifetime definition combination I tried. I don't understand why I need the lifetime definition. The ownership is moved in any step so for my understanding its clear that Holder struct is the owner for all data. Is it?

How can I correct my code to compile?

Thanks for help.

1
Without static lifetime you can solve this issue by just expecting boxed BaseStuff : fn register(&mut self, data: Box<BaseStuff> ) , please check the playground.Ömer Erden

1 Answers

4
votes

You almost got it - the issue here is that the type for which BaseStuff is implemented may be a reference (e.g. impl BaseStuff for &SomeType). This means that even though you're passing data by value, the value may be a reference that will be outlived by your Box.

The way to fix this is to add a constraint such that the object has a 'static lifetime, meaning it will either be a value type or a static reference. You can apply this constraint to the trait or the method accepting the trait, depending on your use case.

Applying the constraint to the trait:

trait BaseStuff: 'static {}

Applying the constraint to the method:

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

If you add the 'static constraint to the method, I would recommend also adding it to the Vec to avoid losing type information, like so:

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