cognitive-engineering-lab / rust-book

The Rust Programming Language: Experimental Edition
https://rust-book.cs.brown.edu
Other
593 stars 92 forks source link

Struct lifetimes could use clarification #210

Open timmc opened 1 week ago

timmc commented 1 week ago

URL to the section(s) of the book with this problem:

https://rust-book.cs.brown.edu/ch10-03-lifetime-syntax.html#lifetime-annotations-in-struct-definitions

Description of the problem:

While the discussion of lifetimes as they relate to functions is quite helpful, there's very little here about how structs are related to lifetimes. Don't struct instances always have to outlive their ref fields? What does it mean for a struct to have multiple lifetime annotations? What is being enforced differently if I have multiple fields with the same lifetime vs. different ones?

Suggested fix:

Broadly, it would help to have those questions answered! I'm still learning Rust, so I can't suggest authoritative answers. However, from IRC, I got the following notes that made sense to me:

willcrichton commented 1 week ago

To answer your specific questions:

Don't struct instances always have to outlive their ref fields?

The opposite, ref fields must outlive structs containing them.

What does it mean for a struct to have multiple lifetime annotations? What is being enforced differently if I have multiple fields with the same lifetime vs. different ones?

A struct can contain references that are guaranteed to live for different lengths of time. Both lengths of time are longer than the struct's lifetime, but are not necessarily equal. For example:

struct Foo<'a, 'b> {
    x: &'a i32,
    y: &'b i32
}

struct Bar<'a> {
    x: &'a i32,
    y: &'a i32
}

fn works() {
    let a = 1;
    let x = &a;
    let z = {
        let b = 2;
        let y = &b;
        let foo = Foo { x, y };
        foo.x
    };
    println!("{z}");
}

fn does_not_work() {
    let a = 1;
    let x = &a;
    let z = {
        let b = 2;
        let y = &b;
        let foo = Bar { x, y };
        foo.x
    };
    println!("{z}");
}

The first function compiles while the second one does not. They are the same except for the struct being used. In the second function, the struct Bar requires its fields have the same lifetime, so Rust conservatively picks the shorter lifetime of &b. In the first function, the struct Foo allows the fields to have different lifetimes, so Rust understands that foo.x lives as long as &a.

The lifetime annotations on structs are mostly for the programmer rather than the compiler.

The annotations are definitely also for the compiler, as shown in the example above.

Your general point about explaining this more in the book is well-taken, I'll see how to work it in.

timmc commented 3 days ago

Oops yes, I wrote that first one backwards. :-D And thanks for the example, that's helpful! Something like that would be really useful in illustrating that concept.

I think the last thing that I'm shaky on is the distinction between the lifetime parameters for the struct reference vs. the struct type. In the book we have fn foo<'a, 'b>(x: &'a ImportantExcerpt<'b>). Here, I infer that 'b is describing the lifetime of references in one or more fields of the struct, while 'a is describing the lifetime of the reference to the struct itself. Hopefully that's correct! If so, making it explicit could be useful.