Why can the lifetimes not be elided in a struct definition?

When you define a struct, you aren't making a relation between the lifetime of the struct and the lifetime of the fields. As you point out, the references in the fields have to live longer than the struct.

Instead, what you are doing is providing a "generic lifetime" that will be specialized when you create the struct. This is similar to having a struct with a type parameter:

struct Foo<T>
    foo: T,
}

When you construct the struct, appropriate lifetimes (or types) will be inserted by the compiler, and then it checks that everything still works out.

The other thing is that you can specify the lifetimes with respect to each other:

struct Line<'a, 'b: 'a> {
    start: &'a Point,
    end: &'b Point,
}

This says that start and end can have different lifetimes, so long as the lifetime of end outlives the lifetime of start.

why doesn't the compiler do lifetime elision for structs? It seems in the spirit of Rust to do so

(emphasis mine)

I actually believe that Rust tends towards explicitness, especially when it comes to defining top-level items (like functions, structs).

The rules for lifetime elision for functions have a pretty small scope and were empirically found in RFC 141 to have a high success rate (87%). This was a very good ergonomic return on investment.

Perhaps at some point, similar elision will occur for structs, but it hasn't been a big enough problem yet. If you feel strongly about this, then I'd highly recommend asking for consensus on the user forum, progressing to the developer forum, then ultimately making an RFC.

RFC 2093 adds a small amount of inference. Before it is implemented, you have to express that a generic type as a reference needs to outlive the reference:

struct Foo<'a, T: 'a> {
    start: &'a T,
}

There's no case in which you wouldn't want this bound, so after the RFC is implemented, you can just say:

struct Foo<'a, T> {
    start: &'a T,
}

Suppose we have a constructor for Line:

impl<'a> Line<'a> {
    fn new(start: &'a Point, end: &'a Point) -> Line<'a> { // '
        Line {
            start: start,
            end: end,
        }
    }
}

new returns a Line<'a>. To be able to parameterize a type with a lifetime (as we do here with Line<'a>), this type must define lifetime parameters! Although the compiler could automatically define lifetime parameters when necessary, it's much easier to figure out that a type has lifetime parameters (or not) by just looking at its definition in the source code.

Lifetime parameters on structs and enums play an important role in the borrow checker. They let the compiler know that a struct retains borrows to some values. The compiler can then return errors when you try to mutate a value that has active borrows.

fn main() {
    let mut start = Point { x: 2, y: 4 };
    let end = Point { x: 7, y: 10 };
    let line = Line::new(&start, &end);
    start.x = 3; // error: cannot assign to `start.x` because it is borrowed
}

Tags:

Lifetime

Rust