Colonel Thirty Two
Colonel Thirty Two

Reputation: 26539

Lifetime doesn't outlive, but only when structure has a trait object in it

Code:


trait MyTrait {
    fn update(&self, _prev: &Self) { unimplemented!() }
}

trait MyOtherTrait<'a> {}

pub struct MyTraitDynWrapper<'a> {
    // When v1 commented out, code works
    v1: &'a dyn MyOtherTrait<'a>,
    
    v2: &'a (),
}
impl<'a> MyTrait for MyTraitDynWrapper<'a> {}

struct Container;
impl Container {
    // GENERATED BY PROC MACRO: CANNOT CHANGE
    fn with_ref<'outer_borrow, ReturnType>(
        &'outer_borrow self,
        user: impl for<'this> FnOnce(&'outer_borrow MyTraitDynWrapper<'this>) -> ReturnType,
    ) -> ReturnType {
        unimplemented!()
    }
}

fn main() {
    let a = Container;
    let b = Container;

    a.with_ref(|a| {
        b.with_ref(|b| {
            MyTrait::update(a, b)
        })
    });
}

When compiling as-is, I get a lifetime errors complaining, in main, that the lifetime of b in the closure may not outlive the lifetime of a, even though it should be fine since the call doesn't borrow anything for longer than the call.

If I comment out the v1 field of MyTraitDynWrapper, the code compiles fine.

Container::with_ref is a simplification of a method that the ouroboros crate generates (its similar to rental), and I cannot change it.

What's happening with this code and how can I fix the error while still being able to use the trait object?

Error:

error[E0308]: mismatched types
  --> src/lib.rs:33:23
   |
33 |             MyTrait::update(a, b)
   |                                ^ lifetime mismatch
   |
   = note: expected reference `&MyTraitDynWrapper<'_>`
              found reference `&MyTraitDynWrapper<'_>`
note: the anonymous lifetime #2 defined on the body at 32:14...
  --> src/lib.rs:32:14
   |
32 |           b.with_ref(|b| {
   |  ____________________^
33 | |             MyTrait::update(a, b)
34 | |         })
   | |_________^
note: ...does not necessarily outlive the anonymous lifetime #2 defined on the body at 31:13
  --> src/lib.rs:31:13
   |
31 |       a.with_ref(|a| {
   |  ________________^
32 | |         b.with_ref(|b| {
33 | |             MyTrait::update(a, b)
34 | |         })
35 | |     });
   | |_____^

error[E0308]: mismatched types
  --> src/lib.rs:33:23
   |
33 |             MyTrait::update(a, b)
   |                                ^ lifetime mismatch
   |
   = note: expected reference `&MyTraitDynWrapper<'_>`
              found reference `&MyTraitDynWrapper<'_>`
note: the anonymous lifetime #2 defined on the body at 31:13...
  --> src/lib.rs:31:13
   |
31 |       a.with_ref(|a| {
   |  ________________^
32 | |         b.with_ref(|b| {
33 | |             MyTrait::update(a, b)
34 | |         })
35 | |     });
   | |_____^
note: ...does not necessarily outlive the anonymous lifetime #2 defined on the body at 32:14
  --> src/lib.rs:32:14
   |
32 |           b.with_ref(|b| {
   |  ____________________^
33 | |             MyTrait::update(a, b)
34 | |         })
   | |_________^

error: aborting due to 2 previous errors

Upvotes: 1

Views: 217

Answers (1)

trent
trent

Reputation: 27885

&'a dyn Trait<'a> has many the same problems &'a mut Struct<'a> does. Because traits (and therefore trait objects) are invariant over their lifetime parameters, once you put 'a in a trait, the compiler can no longer vary it to try to satisfy lifetime constraints.

You cannot tell the compiler that a trait is covariant, but if you can't simply remove the lifetime parameter from MyOtherTrait entirely, you might use a higher-ranked trait bound (HRTB) to say that MyOtherTrait is not parameterized with 'a within MyTraitDynWrapper<'a>:

pub struct MyTraitDynWrapper<'a> {
    v1: &'a dyn for<'b> MyOtherTrait<'b>,
    v2: &'a (),
}

If not for the constraint that you can't change with_ref, you could make 'a and 'b both parameters of MyTraitDynWrapper:

pub struct MyTraitDynWrapper<'a, 'b> {
    v1: &'a dyn MyOtherTrait<'b>,
    v2: &'a (),
}

// ...

impl Container {
    // GENERATED BY PROC MACRO: CANNOT CHANGE
    fn with_ref<'outer_borrow, 'that, ReturnType>(
        &'outer_borrow self,
        user: impl for<'this> FnOnce(&'outer_borrow MyTraitDynWrapper<'this, 'that>) -> ReturnType,
    ) -> ReturnType {
        unimplemented!()
    }
}

However, if none of these options work for you, it's not clear what else you could do that would be sound. Bear in mind that traits can be implemented by all kinds of types, so the compiler is protecting you not just from your own code but from any code that might be written.

See also

Upvotes: 1

Related Questions