Why is the lifetime in impl blocks in a different position?

Hi all! I was studying the Rust lifetimes, and I noticed that in functions, methods and structs, the lifetime generic comes after the identifier symbol:

struct MyStruct<a'> { ... }

fn my_function<a'> { ... }

but for traits it is after the keyword:

impl<'a> Trait for Struct<'a> { ... }

Why is that? Wouldn't have

impl Trait<'a> for Struct<'a> { }

been good? And secondly, why then not using

fn<'a> my_function() {}

Thank you!

This would mean something different: that the trait has a lifetime parameter. This is valid:

impl Trait<'static> for Struct<'static> {}

impl<'a> introduces a lifetime variable that then can be used:

impl<'a, 'b> Trait<'a> for Struct<'b> {}

The difference vs your other examples is that impl Trait is not the definition of a trait. It's a use of the trait that is defined elsewhere. The definition has lifetime parameters to the right:

trait Trait<'a> {}
3 Likes

It kind of is in the same position, it's just that impl blocks don't have a name and the other examples do.

As for not using fn<'a> func(), probably to keep it consistent with how generics are used: let x: Vec<i32> not let x: <i32>Vec. This also keeps Rust consistent with C++, Java, C#, etc.

3 Likes

Thank you so much to both, you really helped me! I also found two relevant excerpts from the book that explained it (and gave me the ha-ha moment):

  • reading the chapter about lifetimes, I realised this is not a special syntax for lifetimes, it's the same syntax used for generics, because when we write
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

we are meaning (emphasis added)

for some lifetime 'a , the function takes two parameters, both of which are string slices that live at least as long as lifetime 'a

so we are actually declaring a generic lifetime variable, that will later be substituted automatically by the compiler:

the concrete lifetime that is substituted for 'a is the part of the scope of x that overlaps with the scope of y

It thus make sense to use the lifetime annotations in the same position of generic.

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

Note that we have to declare T just after impl so we can use T to specify that we’re implementing methods on the type Point<T> . By declaring T as a generic type after impl , Rust can identify that the type in the angle brackets in Point is a generic type rather than a concrete type. We could have chosen a different name for this generic parameter than the generic parameter declared in the struct definition, but using the same name is conventional. Methods written within an impl that declares the generic type will be defined on any instance of the type, no matter what concrete type ends up substituting for the generic type.

I think this explains it well, and by the way, IIUC is what allows us to also specialize the trait like impl Point<i32> { }. And as you both said, in this latter case the generic is after the symbol because it is referred to the struct, not the impl block, and it's being used, not declared. Whereas, in the case impl<T> Point<T>, it is first declared, then used (even if, being it a variable, it is not substituted by a concrete type, and it stays a generic)

4 Likes

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.