Role of lifetime parameter in impl

Hi. Is it always true that the following two implementations of MyStruct are equivalent? I'm particularly interested in the role of the lifetime parameter <'tcx>.

struct MyStruct;

impl<'tcx> MyStruct {
    fn foo(&mut self) {
        // ...
    }

    fn bar(&mut self) {
        // ...
        self.foo();
    }
}
struct MyStruct;

impl MyStruct { // <-- NOTE the missing lifetime
    fn foo(&mut self) {
        // ...
    }

    fn bar(&mut self) {
        // ...
        self.foo();
    }
}

Hi, if you don't use it anywhere you can remove it and there will be no consequences.

impl<stuff> is the type level analog of let stuff;. It just declares names for lifetimes or type "variables", so that when you use them later somewhere in the impl, the compiler knows this name is expected and it's not made-up or a typo.

Well, there's more to it than that. Something like this would be forbidden for a regular type parameter:

error[E0207]: the type parameter `A` is not constrained by the impl trait, self type, or predicates
 --> src/lib.rs:2:6
  |
2 | impl<A> Struct {}
  |      ^ unconstrained type parameter

...though this is mostly to prevent you from using it inside the impl when it isn't constrained by the trait or type (as this could complicate monomorphization). Lifetimes don't affect monomorphization, so the compiler is far more liberal with how they can be used.

It's a bug:

https://github.com/rust-lang/rust/issues/41960

2 Likes

Essentially an impl<'Lifetimes, Types> is used to either:

  • Apply the types/lifetimes to the type/trait/trait impl you're impling:
struct Bar<T>(T);
impl<T> Bar<T> {
    fn i_can_use_T() {}
}
trait Foo<T> {}
impl<A, B> Foo<A> for Bar<B> {}
  • Apply additional constraints for the types you're impling:
impl<T: ?Sized> {
    fn T_may_not_be_sized() {}
}

Thank you for the answers!