Impl for a trait and weird lifetime error


#1

I’m not sure if this is a feature or a bug, but I feel like I’ve fallen down a rabbit hole into wonderland because I haven’t seen documentation for any of this. I discovered you can do something like this:

trait Foo{}
impl Foo {
  fn ref_me(self: Box<Foo>) -> Rc<Box<Foo>> {
    Rc::new(self)
  }
}
struct Baz;
impl Foo for Baz {}
let a: Box<Foo> = Box::new(Baz);
let b: Rc<Box<Foo>> = a.ref_me();

on the latest rust nightly this compiles and runs with no warnings or errors. This is kind of baffling to me because until now I didn’t realize traits could have their own impl that wasn’t for a struct. Is this an impl on the trait object, or is something else going on here?

But it gets even weirder. Suppose I put a harmless looking method in the impl like this:

impl Foo {
  fn wat(&self) -> i32 { 7 }
}

so this works fine:

let x: Baz = Baz;
let y: &Foo = &x;

but as soon as I add this line

let z = y.wat();

I get this error

src/test.rs:301:20: 301:21 error: `x` does not live long enough
src/test.rs:301     let y: &Foo = &x;
                                   ^
note: reference must be valid for the static lifetime...
src/test.rs:300:22: 316:2 note: ...but borrowed value is only valid for the block suffix following statement 15 at 300:21

and now I’m totally confused. Can anyone shed some light on what’s going on here?


#2

I can only shed some light on how to fix it, not on what’s going on.

Add a 'static bound to the trait:

trait Foo : 'static { }

#3

You can add a lifetime parameter like this Playpen link

trait Foo { }

impl<'a> Foo + 'a {
    fn wat(&self) -> i32 { 7 }
}

fn main() {
    struct Baz;
    impl Foo for Baz {}
    
    let x: Baz = Baz;
    let y: &Foo = &x;
    println!("{}", y.wat());
    
}

#4

I am puzzled by the same question. Could you explain what’s going on? Why does impl Trait assume a default 'static lifetime?


#5

Traits can be implemented for many types, including ones that hold references. Since a trait object has the real type erased, Rust has no way to reason about the lifetimes of any references the real type may hold. So, the default is that it is 'static (same default as Box, for example). Alternatively, you can put a lifetime bound on the trait and then Rust can work out the lifetimes based on the scope where the trait object is used.

This is by no means an authoritative answer, but rather my understanding of it.


#6

But impl<'a> Foo + 'a doesn’t actually put a lifetime bound, because 'a is an unbounded lifetime, isn’t it? In other words, 'a can be any lifetime, including 'static, so why not making

impl<'a> Foo + 'a

the default instead of the current

impl Foo + 'static

?


#7

According to this RFC, it seems to already default to your first option. I have no idea what you and everyone else is talking about here.


#8

I don’t think that you were talking about the same problem. Here we are talking about impl Trait without any specific type, and why it defaults to the static lifetime. I don’t think that the problem is related to lifetime elision, which you mentioned.


#9

Maybe this RFC is relevant?


#10

I can explain Rust well, but I can’t explain why this detail was designed that way. That’s a different kind of why, you’d need the language historians and those that can interpret the spirit and idea of a particular change.


#11

I wonder if it’s because 'static used to be the only object bound option, except it had to be spelled out explicitly - that (“hidden”) default was carried forward when lifetime bounds were introduced.

Rust also seems to prefer explicitness in type definitions, whereas it’ll elide/coerce/etc in fn contexts.


#12

It’s just that there’s a lot of RFCs about this: https://github.com/rust-lang/rfcs/blob/1f5d3a9512ba08390a2226aa71a5fe9e277954fb/text/0192-bounds-on-object-and-generic-types.md