Confusion about struct variance with &mut dyn

Hello,

I'm a bit confused by the following Rust error: Rust Playground

trait Trait {}

struct Impl;
impl Trait for Impl {}

struct Holder<'r> {
    r: &'r mut dyn Trait,
}

fn static_holder() -> Holder<'static> {
    Holder{ r: Box::leak(Box::new(Impl)) }
}

impl<'r> Default for Holder<'r> {
    fn default() -> Holder<'r> {
        static_holder()
    }
}

I'm getting:

error: lifetime may not live long enough
  --> src/lib.rs:16:9
   |
14 | impl<'r> Default for Holder<'r> {
   |      -- lifetime `'r` defined here
15 |     fn default() -> Holder<'r> {
16 |         static_holder()
   |         ^^^^^^^^^^^^^^^ returning this value requires that `'r` must outlive `'static`
   |
   = note: requirement occurs because of the type `Holder<'_>`, which makes the generic argument `'_` invariant
   = note: the struct `Holder<'r>` is invariant over the parameter `'r`
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
  1. Why is Holder<'r> invariant over r? The linked nomicon says &'r mut T is covariant over 'r.
  2. Why does it work when I remove the dyn Trait and instead hardcode Impl? Rust Playground
  3. Why does it work when I manually deconstruct static_holder and reconstruct a new Holder instance? Rust Playground

Thanks!
Jonathan

Trait objects have a default lifetime. Looks to me that this rule is making your type invariant. Explicitly setting the lifetime instead of eliding it will override the default behaviour.

struct Holder<'r> {
    r: &'r mut dyn Trait,
}

That's a &'r mut (dyn Trait + 'r),[1] so T = dyn Trait + 'r. T is invariant in &mut T, so 'r is invariant in Holder<'r>.

You actually can coerce the inner lifetime to something shorter sometimes, but the lifetime is invariant in Holder<'r>, so you can't utilize that directly. In that playground, you've drilled down to a level where the unsizing coercion is possible. You could put that reconstruction in a method as an alternative to having two lifetimes.

There's no elided lifetime.

Be aware that those docs are also quite inaccurate, particularly if there are trait bounds. That said, there are none in this example, so those docs are probably "good enough" for this conversation.


  1. here's my summary of the elision rules ↩︎

Ahh, thanks a lot!

I've decided to make static_holder generic over its returned lifetime, and then I don't need any coercion to begin with: Rust Playground