Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

struct Point {
    x: u32,
    y: u32,
}

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

Here, the only possible option for the start and end fields is to have a lifetime the same or longer than the Line variable that contains them. I can't even imagine how one will go about using a lifetime specifier to say that the fields have a shorter lifespan.

Why do I have to explicitly specify a lifetime here? Is elision not possible in this situation and if so why not?

like image 464
RajV Avatar asked Jan 05 '15 18:01

RajV


People also ask

When should lifetimes be attached to the function parameters?

On Function Declaration Input and output parameters with references should attach lifetimes after the & sign. After the function name, we should mention that the given lifetimes are generic types. ex. fn foo<'a> (..) , fn foo<'a, 'b> (..) 02. On Struct or Enum Declaration Elements with references should attach lifetimes after the & sign.

What is lifetime elision in C++?

When writing functions that accept references as arguments, the compiler can infer the correct lifetimes in many cases, saving you the trouble of writing them out by hand. When lifetime annotations are implicit, we call this lifetime elision. The compiler uses three rules to figure out whether lifetime annotations can be elided or not.

What are lifetimes and why are they important?

In particular, lifetimes are important to keep in mind when returning references from functions and when creating structs with references. These are both common situations, and it’s easy to get lost if you don’t understand what’s going on.

What is the use of lifetimes in C++?

Like borrows, lifetimes are checked at compile time, which means your program can’t compile if the borrow checker deems the references invalid. In particular, lifetimes are important to keep in mind when returning references from functions and when creating structs with references.


Video Answer


2 Answers

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
}
like image 36
Francis Gagné Avatar answered Oct 06 '22 14:10

Francis Gagné


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,
}
like image 81
Shepmaster Avatar answered Oct 06 '22 14:10

Shepmaster