1

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
Colonel Thirty Two
  • 23,953
  • 8
  • 45
  • 85

1 Answers1

1

&'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

trent
  • 25,033
  • 7
  • 51
  • 90
  • So TLDR once I have `dyn Trait<'a>`, its only implemented for the lifetime `'a` and nothing else? It's a bit weird that I don't need to bound the `update` method then, and rather it causes an error in the surrounding code that uses it because the struct simply happens to have a trait object. This is useful though; I think I can remove the lifetime from the trait object. – Colonel Thirty Two Oct 16 '20 at 13:40
  • Essentially, yes. It would be slightly more accurate to say that `dyn Trait<'a>` is only *known* to implement `Trait<'a>`; the underlying type may well implement others, just as it may implement `Debug`, `Copy`, etc. but you can't use those through a `dyn Trait<'a>` either. – trent Oct 16 '20 at 13:42