Why is 'static lifetime required here? Compiler bug or am I missing something?

So I have this simple piece of code:

trait Foo: 'static {  fn dummy(&self);  }
impl<T: 'static> Foo for T {  fn dummy(&self) {}  }

// No problem compiling
fn bar1(foo: &dyn Foo) {
    print!("{:?}", foo.dummy());
}

// Compilation error: 'static lifetime required for foo
fn bar2(foo: &mut dyn Foo) {
    print!("{:?}", foo.dummy());
}

and I get this error when compiled:

error[E0621]: explicit lifetime required in the type of `foo`
  --> src/main.rs:11:24
     |
  10 | fn bar2(foo: &mut dyn Foo) {
     |         --- consider changing the type of `foo` to `&'static mut (dyn Foo + 'static)`
  11 |     print!("{:?}", foo.dummy());
     |                        ^ lifetime `'static` required

I can't figure out why does using a mut reference suddenly causes it to require the lifetime to be 'static? Could it be a compiler error or is there something going on with mutable trait references that I didn't know of?

Edit: Playground link

My guess is because:

impl<T: 'static> Foo for T {  fn dummy(&self) {}  }

Foo here is implemented only for things that are 'static (<T: 'static> == every T must also be 'static)

That's what I thought at first too, but wouldn't the compiler reject the shared reference in function bar1 in this case too?

Moreover it doesn't really make much sense to enforce that the lifetime of the reference be equal to the lifetime of the trait in the first place. Intuition says it only needs to be shorter. Consider any type that doesn't hold references internally (e.g. i32) - such a type is 'static and a reference to it shouldn't have to be 'static.

It may seem confusing but this is how types work in Rust. T, &T, and &mut T are all different types.

In this case, & dyn Foo and &mut dyn Foo are the types, and dummy() takes a reference to them - so the fully expanded types become dummy(self: && dyn Foo) and dummy(self: &&mut dyn Foo).

I don’t think your bar1 will be callable with non static objects behind the trait, even though it compiles. It compiles perhaps because compiler allows variance of immutable refs, whereas it doesn’t with mutable and thus requests explicit lifetime parameter. It may also be a bug - haven’t thought too hard about it.

1 Like

Add to @vitalyd
The . operator is overloaded. The compiler auto-dereferences when the default type does not work. In this case the default type sort of works under some circumstances, ie 'static.

If you explicitly dereference (*foo).dummy() the code compiles.

4 Likes

Yeah, so I think this is an "unfortunate" auto-referencing that @jonh mentioned. Using UFCS it works:

fn bar2(foo: &mut dyn Foo) {
    print!("{:?}", Foo::dummy(foo)); // this is equivalent to Foo:dummy(&*foo), i.e. reborrow
}

But what appears to be happening in the method call syntax is roughly:

fn bar2(foo: &mut dyn Foo) {
    print!("{:?}", Foo::dummy(&foo)); // Note the extra reference taken
}

I think this may warrant a bug report as it's an inconsistency that is likely incidental.

2 Likes

Thanks @vitalyd and @jonh. Indeed when I wrote the code I intended to mean this:

fn bar2(foo: &mut dyn Foo) {
   print!("{:?}", Foo::dummy(foo)); // this is equivalent to Foo:dummy(&*foo), i.e. reborrow
}

I'm not sure if it warrants a bug report, although I think it would be more intuitive if the compiler chose the former rather than the latter. Any idea where do I raise this for further discussion?

It seems like a papercut inconsistency, and may very well be a known issue already. In general, I think everyone is interested in eliminating papercuts as much as possible. So, Issues · rust-lang/rust · GitHub would be the place to file an issue for further discussion.

1 Like