What does it mean to return `dyn Error + 'static`?

Given some generic lifetime parameter 'borrow,

  • the type &'borrow dyn Error is actually sugar for &'borrow (dyn Error + 'borrow).

So, you question becomes:

&'static (dyn Error + 'static)

vs.

&'borrow (dyn Error + 'static)

For which the answer should now be quite clear: the former is not tied to 'borrow and can thus be held arbitrarily long (e.g., by another thread). This is called "being 'static" and is written as follows:

&'static (dyn Error + 'static) : 'static
  • read as: a 'static borrow to an object that itself is 'static (i.e., that can be held arbitrarily long), can itself (the borrow!) be held arbitrarily long.

whereas the latter is tied to the 'borrow lifetime, so
&'borrow (dyn Error + 'static) : 'borrow

  • read as: as reference with lifetime 'borrow to something that can be held (owned) arbitrarily long can itself (the borrow!) only be held for the lifetime borrow. That's because the borrow is the most restrictive here thing, and this property is paramount for the soundness. Even if the pointee could be owned arbitrarily long, we do not own it, so it may not live that long (imagine let x = 42; &x: integers can live arbitrarily long when owned but the &x reference itself cannot, it won't be valid after x is dropped).

I think your real question / where the confusion comes from is instead regarding the two following types:

&'borrow (dyn Error + 'static)
  • the pointee, when owned, can be held arbitrarily long,

vs.

&'borrow (dyn Error + 'borrow)
  • the pointee, when owned, can be held only for the lifetime 'borrow,

Since both types are : 'borrow only (they can only be held for as long as the borrow is active / the lifetime 'borrow) it can be hard to tell the difference. In the case of the Error trait, there is "actually no practical difference" (even if these two types are distinct!), except for a current technical limitation having to do with downcasting (a very important feature with type erased errors!), as @mbrubeck very aptly pointed out.


Now, let's try to better grasp the difference between &'short (dyn Trait + 'short) and &'short (dyn Trait + 'static).

FIrst of all, by variance (an advanced topic), the latter subtypes the former, meaning that whatever the former can do, the latter can also do. So if there is a difference, it is in something that can be done with the latter but that the former cannot do.

Since, as I mentioned, the difference lies in owning the pointee, let's use an operation that goes fromnm &'_borrow (dyn Trait + '_self) to dyn Trait + '_self (a clone for dyn Trait + '_self). Here is an example:

// i32 : Trait

fn former<'borrow> (it: &'borrow i32) -> &'borrow (dyn Trait + 'borrow)
{
    it
}

fn latter<'borrow> (it: &'borrow i32) -> &'borrow (dyn Trait + 'static)
{
    it
}

fn clone<'borrow, '_self> (it: &'borrow (dyn Trait + '_self))
  -> Box<dyn Trait + '_self>
{
    it.clone_box()
}

fn main ()
{
    let (_cloned_x_former, _cloned_x_latter) = {
        let x = 42;
        let former = former(&x); // &'borrow (dyn Trait + 'borrow)
        let latter = latter(&x); // &'borrow (dyn Trait + 'static)
        (
            clone(former), // Box<dyn Trait + 'borrow>
            clone(latter), // Box<dyn Trait + 'static>
        )
        // <-- 'borrow cannot go beyond this scope
    }; // error _cloned_x_former: Box<dyn Trait + 'borrow> cannot outlive `'borrow`
}

I think it is best to study that code in detail, and I expect an "aha!" moment to spontaneously happen afterwards: you ought to grasp the difference between the two types and the two lifetime spots :slightly_smiling_face:


And here is a Playground where "_self" is not 'static, to see these lifetimes annotations on dyn itself are paramount for soundness.

4 Likes