#Weird lifetime issue when impl dyn trait

1 messages · Page 1 of 1 (latest)

dapper elbow
#
pub struct TestStruct;

trait TestTrait {
    fn trait_method(&self) -> &'static str;
}

impl TestTrait for TestStruct {
    fn trait_method(&self) -> &'static str {
        "test"
    }
}

impl dyn TestTrait {
    fn dyn_method(&self) -> String {
        format!("asd-{}", self.trait_method())
    }
}

struct Container<'a> {
    items: Vec<&'a dyn TestTrait>,
}

When compiling the below main function, there is no issue:

fn main() {
    let test = TestStruct;

    let list = vec![
        &test as &dyn TestTrait,
        &test as &dyn TestTrait,
        &test as &dyn TestTrait,
    ];

    let container = Container { items: list };

    container.items.iter().for_each(|item| {
        println!("{}", item.trait_method());
    });
}

but when switching out the trait_method for a dyn impl method, suddenly there are compile errors. The following:

fn main() {
    let test = TestStruct;

    let list = vec![
        &test as &dyn TestTrait,
        &test as &dyn TestTrait,
        &test as &dyn TestTrait,
    ];

    let container = Container { items: list };

    container.items.iter().for_each(|item| {
        println!("{}", item.dyn_method());
    });
}

yields error:

borrowed value does not live long enough
cast requires that test is borrowed for 'static
Why?

hot iron
#

If you do

impl<'a> dyn TestTrait + 'a {
```it works. Not entirely sure why though.
dapper elbow
keen patrol
#

dyn Trait without a lifetime gets its lifetime from context

#

If there is no context then 'static is assumed

hot iron
#

Wouldn't that mean both of them are dyn TestTrait + 'static?

keen patrol
#

No, the context of &'a dyn Trait is 'a

dapper elbow
#

But why would the dyn method promote the lifetime when it doesn't have to?

hot iron
#

But that's the lifetime of the reference (in the method, it's the lifetime on &self) and not the lifetime of the trait object.

keen patrol
#

You’ve only defined the impl for 'static

dapper elbow
keen patrol
#

impl dyn Trait is the same as impl dyn Trait + 'static

dapper elbow
#

Okay so that is it then

hot iron
#

Yeah but fn dyn_method(&self) is fn dyn_method<'b>(&'b self)

dapper elbow
keen patrol
#

That method is only defined on the 'static dyn Trait tho

keen patrol
#

It’s so you don’t have to keep writing dyn Trait + 'static when storing stuff in structs

dapper elbow
hot iron
#

I'm guessing this is some variance thing where &'a dyn Trait + 'static gets downgraded to &'a dyn Trait + 'a.

cold scarab
#

Like Dawn said, dyn Trait + '_ gets the '_ from context if not specified (or explicitly elided), or assumes 'static if there's no context.

Since you have rs struct Container<'a> { items: Vec<&'a dyn TestTrait>, } // which is implicitly struct Container<'a> { items: Vec<&'a (dyn TestTrait + 'a)>, } instead of ```rs
struct Container<'a> {
items: Vec<&'a (dyn TestTrait + 'static)>,
}
// or
struct Container<'a, 'b> {
items: Vec<&'a (dyn TestTrait + 'b)>,
}

*and* ```rs
impl dyn TestTrait { ... }
// which is implicitly 
impl dyn TestTrait + 'static { ... }
```, instead of ```rs
impl dyn TestTrait + '_ { ... }
// same as 
impl<'a> dyn TestTrait + 'a { ... }

These together lead the compiler to conclude that 'a = 'static, since dyn_method is only callable on dyn TestTrait + 'static, and container holds &'a (dyn TestTrait + 'a) that have dyn_method called on them.
Which then leads the compiler to conclude that test must be borrowed for 'static, which it can't be because it's a local variable that gets dropped at the end of fn main

hot iron
#

Ahhhhhh, okay so Container needs another lifetime arg.

hot iron
#

Okay just to conclude this, if you give Container another lifetime parameter, it'll compile.

struct Container<'a, 'b> {
    items: Vec<&'a (dyn TestTrait + 'b)>,
}
```But you should also add a lifetime to the dyn impl so that it works with more implementers of the trait, not just `'static` ones like `TestStruct`.
```rs
impl<'b> dyn TestTrait + 'b {
    fn dyn_method(&self) -> String {
        format!("asd-{}", self.trait_method())
    }
}
```I'm not sure this is a good idea anyway. I'd make this a method on the trait itself with a default implementation. It might make sense if you are checking `type_id` or trying to avoid the dynamic dispatch or something like that.