Questions about generic parameter and lifetime

Hi, guys. Recently I'm trying to understand why rust force me to put lifetime annotations before reference fields in a struct. However, I met a confusing case:

pub struct MyStruct<T> {
    pub t: T
    // when `T` is deduced to `&String` type, compile succeed.
}

pub struct RefStruct {
    pub a: &String
    // we must put a lifetime annotation here to tell compiler that `a` outlives `RefStruct`
}

fn main() {
    let a: MyStruct<&String>;
}

As you can see, replacing references of a explicit type to a generic one "bypassed" the requirement of lifetime bound. I wonder if generic parameters already have implicit lifetime annotations.

Moreover, as mentioned in rust documentation, lifetime annotations tell compiler that structs should outlive references in it. And we need to add those annotations for every references, which seems a redundant work. So why don't compiler automatically detect references and add lifetimes for them?

Thank you.

Using references via generic types simplifies lifetime annotations, but it's not a loophole for anything. All lifetime rules still apply.

Generic types don't have implicit lifetime annotations (well, except lifetime elision). It works the other way: you will be allowed to use a reference in place of a generic type only if it meets all requirements of the generic type. For example, you can't use &mut u32 where T: Copy is required.

This is why some generic code (e.g. thread::spawn) uses T: 'static requirement, so that types with temporary references won't be allowed.

1 Like

An important thing to note here is that MyStruct<&String> is just an abbreviation for MyStruct<&'_ String>. There is still a place for a lifetime argument. The situation is completely analogous to

pub struct RefStruct<'l> {
    pub a: &'l String
}

where you can write RefStruct<'_> to “elide” the lifetime argument. Rust even allows you to write RefStruct without any lifetime arguments, though using this feature is somewhat discouraged because hiding the presence of a lifetime argument like this can be confusing. (Still, it’s not a loophole.)

Actually it’s the other way around, a reference in a struct must outlive the struct it’s contained in. Also this hasn’t got too much to do with why a lifetime “annotation” is needed.

This is not quite accurate. The reason why a lifetime parameter is needed is to that there’s a way to specify the lifetime of the reference in the field a of RefStruct. If you want to put a Vec into a struct field, you can’t write

pub struct VecStruct {
    pub a: Vec
}

either. It has to be

pub struct VecStruct<T> {
    pub a: Vec<T>
}

In general, lifetime annotations are never needed because you need to “tell the compiler that you aren’t doing something wrong”. The compiler will know to check for itself if you’re violating any important rules. Something like “we must […] tell compiler that a outlives RefStruct doesn’t make too much sense because it seems to assume that “a has to outlive RefStruct in order to not violate Rust’s borrowing rules” and “we need to tell the compiler that we aren’t breaking the rules”. Instead, lifetime annotations are always needed to clarify or specify interactions of lifetimes. There’s usually some choice involved, you’ll tell the compiler which of multiple options you choose and the compiler will work with that. You’ll specify some interface (in the form of a function/method signature) and the compiler will ensure that both the implementation and the callers will not violate the signature you wrote.

In the case of structs, there’s the choice if the lifetime of the a: &'_ String field is supposed to be a parameter or some fixed lifetime (in which case it could only be “'static”). Once you have multiple fields with lifetime arguments, there’s the choice of whether to make them all use the same lifetime or to provide multiple lifetime as parameters of the struct, e.g.

pub struct StructOne<'a> {
    pub a: &'a u8,
    pub b: &'a u8,
}

vs.

pub struct StructTwo<'a, 'b> {
    pub a: &'a u8,
    pub b: &'b u8,
}

And for a function fn(&u8, &u8) -> &u8 you’ll need to choose which one of the input parameters the lifetime in the type of the return value should correspond to. Or whether any of them is supposed to be 'static. There’s options to choose like

fn foo<'a, 'b>(x: &'a u8, y: &'b u8) -> &'a u8 { … }
fn foo<'a, 'b>(x: &'a u8, y: &'b u8) -> &'b u8 { … }
fn foo<'a, 'b, 'c>(x: &'a u8, y: &'b u8) -> &'c u8 { … }
fn foo<'a, 'a>(x: &'a u8, y: &'a u8) -> &'a u8 { … }
fn foo<'a, 'b>(x: &'a u8, y: &'b u8) -> &'static u8 { … }
// etc…

By the way, the use of type &String (“shared reference to String”) is also discouraged in Rust, one should use &str instead. This and other idioms and conventions can be enforced easily by using clippy, something I’d recommend to every beginner / language learner of Rust, the tool can teach you a lot.


In general, reference types in Rust have the form &'a T, with an explicit lifetime 'a. There’s two situations when this lifetime can be left out. First, there’s lifetime elision rules, chapter 10 of the book talks about them. Second, you can leave out lifetimes in places where a type signature is not mandatory. The lifetime will then be inferred, this has nothing to do with the elision rules in function and method signatures. Your example

let a: MyStruct<&String>;

is such a case. let a; without a signature is allowed, too, when the compiler can infer the type. You can help type inference by providing more information, e.g. let a: MyStruct<_>; and the compiler will infer the type for where the “_ ” is. Or let a: MyStruct<&'_ String>; and the compiler will infer the lifetime for where the '_ is. Finally, &T is an abbreviation for &'_ T, so writing let a: MyStruct<&String>; is the same.

Another thing you might notice with type inference is that while the compiler always complains when it cannot infer a type (because of some ambiguity, e.g. when you’re only using a variable with a generic function) e.g.

error[E0282]: type annotations needed
 --> src/main.rs:2:9
  |
2 |     let a;
  |         ^ consider giving `a` a type

or

error[E0282]: type annotations needed for `Box<_>`
 --> src/main.rs:2:12
  |
2 |     let a: Box<_>;
  |         -  ^^^^^^ cannot infer type
  |         |
  |         consider giving `a` the explicit type `Box<_>`, with the type parameters specified

it will never complain when a lifetime is underspecified, e.g.

fn foo() {
    let a: &'_ u8; // compiles fine
}
6 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.