qed
qed

Reputation: 23134

Same lifetime parameter for all fields of a struct

Here is the code:

#[derive(Debug)]
struct Foo {
    f: i32,
}

#[derive(Debug)]
struct Bar<'a> {
    bar1: &'a Foo,
    bar2: &'a Foo,
}

#[allow(unused_variables)]
fn make_bar<'a>(foo1: &'a Foo, foo2: &'a Foo) -> Bar<'a> {
    Bar {
        bar1: foo1,
        bar2: foo2,
    }
}

fn extract_bar2<'a>(foo: &'a Foo) -> &'a Foo {
    let foo1 = Foo { f: 22 };
    let foo2 = make_bar(&foo, &foo1).bar1;
    foo2
}

fn main() {
    let foo = Foo { f: 11 };
    let foo1 = extract_bar2(&foo);
    println!("foo1: {:?}", foo1);
}

This gives an error:

error: `foo1` does not live long enough
  --> src/main.rs:23:32
   |>
23 |>     let foo2 = make_bar(&foo, &foo1).bar1;
   |>                                ^^^^
note: reference must be valid for the lifetime 'a as defined on the block at 21:45...
  --> src/main.rs:21:46
   |>
21 |> fn extract_bar2<'a>(foo: &'a Foo) -> &'a Foo {
   |>                                              ^
note: ...but borrowed value is only valid for the block suffix following statement 0 at 22:29
  --> src/main.rs:22:30
   |>
22 |>     let foo1 = Foo { f: 22 };
   |>                              ^

The core question is: What does a lifetime parameter actually means in the context of a struct?

More specifically: What are the consequences of having the same lifetime parameter for all fields of a struct? Do their lifetimes have to be exactly the same? Do they have to overlap? If so to what extent should they overlap?

What are the (semantic and practical) differences between the following two structs?

struct Bar<'b> {
    bar1: &'b Foo,
    bar2: &'b Foo,
}
struct Bar<'a, 'b> {
    bar1: &'a Foo,
    bar2: &'b Foo,
}

Upvotes: 1

Views: 1031

Answers (2)

qed
qed

Reputation: 23134

I will paste an answer from reddit that I am really happy with.

The compiler can combine lifetimes by taking their intersection. That is, if you have 'a and 'b, then there is an intersection 'c of 'a and 'b, during which both lifetimes are 'alive'. I believe it is always the case that this intersection is equal to the shortest of 'a and 'b, because of the way scoping works, but perhaps I am mistaken.

In practice, this means that, when you see a fn<'a>(x: &'a T, y: &'a U) -> &'a V, you can put in a &'static T and a &'b U, and you will get a &'b V, because the intersection of 'static and 'b is 'b.

So, why does your method cause the compiler to complain? Because to the compiler it kinda looks like this (it's not valid syntax):

fn extract_bar2<'a>(foo: &'a Foo) -> &'a Foo {
    'b: {
        let foo1 = Foo { f: 22 };
        'c: { // The next line is wrong
            let foo2: &'a Foo = make_bar<'a>(&'a foo, &'b foo1).bar1;
            'd: {
                return foo2;
            }
        }
    }
}

I've made the scopes more explicit. What happens? The compiler knows that foo2 must have type &'a Foo, because that is what the function returns. So the Bar returned by make_bar must have had lifetime 'a: we couldn't get a &'a Foo out of it otherwise. So we must have called make_bar<'a>. But one of the arguments is wrong! foo2 does not have lifetime 'a, it has lifetime 'b, which is outlived by 'a. If you take the intersection and do this:

    let foo2: &'b Foo = make_bar<'b>(&'a foo, &'b foo1).bar1;

then foo2 does not match the return type.

When you use your second definition of Bar, the code will work, because bar1 and bar2 need not have the same lifetimes in that case. So your second Bar definition is strictly more flexible, but in practice you rarely need that extra flexibility, and the extra lifetime annotations are annoying.

Credit to https://www.reddit.com/user/thiez

Upvotes: 0

aSpex
aSpex

Reputation: 5216

What are the (semantic and practical) differences between the following two structs?

There is a small example to demonstrate the difference:

#[derive(Debug)]
struct Foo;

#[derive(Debug)]
struct Bar1<'b> {
    foo1: &'b Foo,
    foo2: &'b Foo,
}

#[derive(Debug)]
struct Bar2<'a, 'b> {
    foo1: &'a Foo,
    foo2: &'b Foo,
}

fn main() {//'a -->
    let foo1 = Foo;
    let ref_foo1 = 
    {//'b -->
        let foo2 = Foo;
        //error: `foo2` does not live long enough
        //replace the Bar1 with Bar2 in the row below to fix error 
        let bar = Bar1{foo1:&foo1, foo2:&foo2};
        bar.foo1
    };//--> 'b
    println!("ref_foo1={:?}", ref_foo1);
}//--> 'a

The Bar1 truncates the lifetimes of its members to their intersection. So you can't get a reference to the foo1 with lifetime 'a from the Bar1 struct. You get the reference with lifetime 'b.

I should note that the error message in this case is a bit misleading

Upvotes: 2

Related Questions