Lifetimes are killing me

I'm new to rust .. I come from Typescript / Java / Python / PHP with a very little C++.
But so far I'm loving it except for lifetimes. I hope someone can help.

I tried to simplify the problem I'm having.


trait MyTrait1 {}

trait MyTrait2 {}

struct FirstStruct<'a> {
    something: &'a Box<dyn MyTrait1>,
}

impl<'a> MyTrait2 for FirstStruct<'a> {}

struct SecondStruct<'a> {
    something: &'a Box<dyn MyTrait1>,
}

impl<'a> SecondStruct<'a> {
    fn return_new_trait2(&self) -> Box<dyn MyTrait2> {
        Box::new(FirstStruct {
            something: self.something,
        }) as Box<dyn MyTrait2>
    }
}

Results in this error

16 |   impl<'a> SecondStruct<'a> {
   |        -- lifetime `'a` defined here
17 |       fn return_new_trait2(&self) -> Box<dyn MyTrait2> {
18 | /         Box::new(FirstStruct {
19 | |             something: self.something,
20 | |         }) as Box<dyn MyTrait2>
   | |_______________________________^ returning this value requires that `'a` must outlive `'static`

I'm wanting to create a new FirstStruct with the pointer I already have and return it.
I'm not sure what is `'static' in this, everything seems to be a Box pointer.

Any help would be great!
Thanks in advance!

Hello @MikeColeGuru!

You are making a classic newcomer mistake, which is perfectly fine.

The first advice I will give you is to not use & references inside structs. Once you understand lifetimes more closely, you will understand that it's rarely needed.

In TypeScript and other garbage collected languages "everything" is wrapped in an Rc or Arc for you, which is a reference counted Box. That means that cloning the Rc pointer just increments a number inside the Rc.

This is what I would do in your case:

use std::rc::Rc;

trait MyTrait1 {}

trait MyTrait2 {}

struct FirstStruct {
    something: Rc<dyn MyTrait1>,
}

impl MyTrait2 for FirstStruct {}

struct SecondStruct {
    something: Rc<dyn MyTrait1>,
}

impl SecondStruct {
    fn return_new_trait2(&self) -> Rc<dyn MyTrait2> {
        Rc::new(FirstStruct {
            something: self.something.clone(),
        })
    }
}

Which will cheaply clone the pointer, but not the content when you call return_new_trait2.

Furthermore, I would avoid doing type casting like as Box<dyn MyTrait2>. It is mostly useful for converting primitive types like i32 to f32. In most of the cases it won't help, but it might be needed in some rare cases. As a beginner you should try to avoid it unless you find a good reason to use it.

6 Likes

In most circumstances, Box<dyn Trait> is really Box<dyn Trait + 'static>. Changing things to

impl<'a> SecondStruct<'a> {
    fn return_new_trait2(&self) -> Box<dyn MyTrait2 + '_> {
        Box::new(FirstStruct {
            something: self.something,
        }) 
    }
}

should be enough to fix things.

As a side note, don't use &'a Box<dyn Trait>, see this SO question for details.

7 Likes

Thanks .. that was very helpful.

I just realized that Rc is immutable unless you wrap the subject in a RefCell.

Now I have Rc<Refcell<Something>> instead of just Rc`
This doesn't seem like a nice API.

Thanks for answering it so precisely.

I do wish there was some place that unpacked all of the magic defaults.

I just found out for example that each call like
some_struct.some_function(5)
is really
SomeStruct::some_function(some_struct, 5)

Which makes alot of sense as the first argument is self for methods. It also now makes more sense to why it sometimes complains of borrowing the some_struct which wouldn't have made any sense with the former representation.

Is there anywhere that unpacks all of this hidden default magic?

I agree, but most often you don't need this. Instead of sharing ownership like this, try to design your data structures so that there is only one owner of each thing.

As a beginner coming from a scripting language, Rust may seem unfomortable at first. Most of the time, if you're reaching for a very common pattern from a GC language, it might not work very well in Rust.

If you keep studying patterns & data modelling in Rust, you'll see that the "everything points at everything" model from OOP+GC isn't very common in Rust. And that's OK because there will be other (sometimes better) ways to do things.

1 Like

Yeah it is quite the paradigm shift. I guess I need to start reading other people code for a while and see what the common Rust patterns are.

1 Like

I would recommend reading "The Book", and then a lot of blog posts. URLO (this forum) is a great resource, just visit every day and read random questions & threads. /r/rust on Reddit might be fun if you like Reddit.

I've collected a small list of blog posts that might be helpful:

Rust resources (I have a few posts of my own as well, which you may find by clicking around).

But most of all, I recommend reading https://this-week-in-rust.org/ every week (and go back in time as well). It really is a fantastic resource.

1 Like

This SO question has a few different explanations for exactly what happens under the hood.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.