Reputation: 23134
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
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
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