Why is the `'_` bound required in `impl dyn` here?

Without the '_ this fails to compile, but I do not understand why. It complains with this error:

11 | pub fn convert<'a, T: 'static>(f: &'a dyn Foo) {
   |                --              - `f` is a reference that is only valid in the function body
   |                |
   |                lifetime `'a` defined here
12 |     f.get::<T>().unwrap();
   |     ^^^^^^^^^^^^
   |     |
   |     `f` escapes the function body here
   |     argument requires that `'a` must outlive `'static`

But get takes a reference, so what is being moved? Does someone know what is going on?

Code

extern crate thiserror; // 1.0.40

pub trait Foo {}

impl dyn Foo + '_ { // <----- here
    pub fn get<T: 'static>(&self) -> Option<&T> {
        todo!()
    }
}

pub fn convert<T: 'static>(f: &dyn Foo) {
    f.get::<T>().unwrap();
}

pub trait Bar: 'static {}

impl dyn Bar {
    pub fn get<T: 'static>(&self) -> Option<&T> {
        todo!()
    }
}

pub fn convert2<T: 'static>(f: &dyn Bar) {
    f.get::<T>().unwrap();
}

impl dyn Bar + '_ {
    pub fn get2<T: 'static>(&self) -> Option<&T> {
        todo!()
    }
}

pub fn convert3<T: 'static>(f: &dyn Bar) {
    f.get2::<T>().unwrap();
}

Every dyn Trait has a lifetime parameter (dyn Trait + 'dyn) which has its own special elision rules when entirely omitted. The rules are context sensitive.

This

impl dyn Foo {

Is short for

impl dyn Foo + 'static {

But this

pub fn convert<T: 'static>(f: &dyn Foo) {

Is analogous to

pub fn convert<'dyn, T: 'static>(f: &'dyn (dyn Foo + 'dyn)) {

So you tried to call <dyn Foo + 'static>::get(f) but you only had a dyn Foo + 'dyn. The error pointed to the reference because those lifetimes have to be the same.


With the change

impl dyn Foo + '_ { // <----- here

dyn Foo + 'dyn has the get method for every 'dyn, and not just dyn Foo + 'static.

6 Likes

Ah fun.

Is adding '_ backwards compatible? Since it allows it to be used in more situations, I feel it might be?

It is in this case, you're basically just adding implementations.

(It is in most cases, but I could probably construct some case where it wasn't.)

1 Like

Alright, thank you a lot!

One thing that I am not sure about is, why does it talk about the reference escaping? Is it because I am trying to 'extend' the lifetime to 'static without being allowed to?

Yeah, something like that. The error would make more sense to me if get directly required a &'static dyn Foo, because methods with 'static bounds often have them so they can do things like send the value to another thread (which might run "forever").

The error is a little off here IMO because get takes a &'any_lifetime (dyn Foo + 'static), and it's only the function signature of convert that implicitly required the reference lifetime be 'static. A better compiler error would point out that the actual mismatch was with the elided trait object lifetime.

Interestingly, even if you allow the reference and trait object lifetimes to differ, it uses the "escapes" language which makes even less sense IMO.

But at least it does suggest the more appropriate fix (when applicable), which is to relax the implementation.

Alright, well the reason I ran into this was because of <dyn std::error::Error>::downcast_ref, so I feel this might not be able to be solved like this...

Downcasting (Any) requires 'static for soundness reasons (lifetimes are erased before runtime), so the most flexible fix in this case may be

 fn convert<T: std::error::Error + Foo + 'static>(
-    e: &dyn std::error::Error
+    e: &(dyn std::error::Error + 'static)
 ) {

The only thing I can see is that Any requires 'static on itself, whereas Error only needs 'static for T in downcast_ref?

The 'static bound on the trait makes the default trait object lifetime for dyn Any be 'static everywhere [1], so the signature of convert2 is already akin to the diff above.


  1. that I have tried; running more exhaustive tests is actually high on my todo list ↩ī¸Ž

1 Like

oh my god. You are saving me here hahaha, I was afraid that it would be impossible to use in my situation. I'll see if it appeases the compiler. Thank you again!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.