Static lifetime not downgrading to bounded lifetime

Hi there,

I am puzzling over this lifetime compilation error, if Foo<'static> is returned then should it not be able to be treated as Foo<'a>?

trait Trait<'a> {
    fn zog(&self) -> &'a str;
}

impl<'a> Trait<'a> for () {
    fn zog(&self) -> &'a str { "" }
}

struct Foo<'a>(Box<dyn Trait<'a>>);

fn foo() -> Result<Foo<'static>, ()> {
    Ok(Foo(Box::new(())))
}

fn bar<'a>() -> Result<Foo<'a>, ()> {
    foo()
}

Error:

error[E0308]: mismatched types
  --> src/lib.rs:16:5
   |
16 |     foo()
   |     ^^^^^ lifetime mismatch
   |
   = note: expected enum `std::result::Result<Foo<'a>, _>`
              found enum `std::result::Result<Foo<'static>, _>`
note: the lifetime `'a` as defined on the function body at 15:8...
  --> src/lib.rs:15:8
   |
15 | fn bar<'a>() -> Result<Foo<'a>, ()> {
   |        ^^
   = note: ...does not necessarily outlive the static lifetime

error: aborting due to previous error

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=20dabbcadde61e3076d7db7da8da9766

1 Like

I believe when type-checking dyn Trait<'a>, the methods that it contains are not taken into account.

This is because it is always backwards-compatible to add new methods to a trait as long as the new methods have a default definition. So everything that compiles with your definition of Trait must also compile when a new method is added to Trait.

Here's an example added method which would make your code unsound:

trait Trait<'a> {
    fn zog(&self) -> &'a str;
    fn bog(&mut self, to_store: &'a str) { }
}

struct StoresBog(Option<&'static str>);
impl Trait<'static> for StoresBog {
    fn zog(&self) -> &'static str {
        self.0.unwrap_or("hi")
    }
    fn bog(&mut self, s: &'static str) {
        self.0 = Some(s);
    }
}

With a trait modification like this, promoting dyn Trait<'static> to dyn Trait<'a> shouldn't be allowed: dyn Trait<'static> only needs to accept &'static str strings, but dyn Trait<'a> must be able to accept any string, even one which is less than static lifetime.

As an example, a struct which implements Trait<'static>, StoresBog, clearly does not implement Trait<'a> for any non-'static 'a. It's only possible to store &'static str in a variable declared for &'static str - you can't pass a str referencing a heap allocated String into StoresBog::bog; StoresBog does not implement Trait<'a>.

In type theory language, dyn Trait<'a> is invariant with respect to 'a. More on variance here.


As a side note, to see that dyn Trait is the problem here and not Foo, this example also fails to compile:

trait Trait<'a> {
    fn zog(&self) -> &'a str;
}

impl<'a> Trait<'a> for () {
    fn zog(&self) -> &'a str { "" }
}

fn foo() ->Box<dyn Trait<'static>> {
    Box::new(())
}

fn bar<'a>() -> Box<dyn Trait<'a>> {
    foo()
}

According to that above link about variance in the nomicon Box<T> is covariant with respect to T. So if dyn Trait<'static> was a subtype of dyn Trait<'a>, then Box<dyn Trait<'static>> would also be a subtype of Box<dyn Trait<'a>>.

3 Likes

Thanks @daboross for the erudite response! Took me a bit to understand, your example helped a lot.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.