What is the reason the following code compiles fine, despite both the lifetimes 'a
and 'b
being independent of each other?
struct Foo<'a> {
i: &'a i32
}
fn func<'a, 'b>(x: &'a Foo<'b>) -> &'b i32 {
x.i
}
fn main() {}
If I make the reference i
in Foo
mutable, it gives the following error.
5 | fn func<'a, 'b>(x: &'a Foo<'b>) -> &'b i32 {
| ----------- -------
| |
| this parameter and the return type are declared with different lifetimes...
6 | x.i
| ^^^ ...but data from `x` is returned here
What is the reason it gives the above error?. Does it consider it's ownership over mutable reference and it sees that something (from Foo
) is being taken out (with an independent lifetime), which is not possible, hence the error ?
This code (which I thought would pass) fails too:
struct Foo<'a> {
i: &'a mut i32
}
fn func<'a, 'b: 'a>(x: &'a Foo<'b>) -> &'b i32 {
x.i
}
fn main() {}
fails with error:
error[E0623]: lifetime mismatch
--> src/main.rs:6:5
|
5 | fn func<'a, 'b: 'a>(x: &'a Foo<'b>) -> &'b i32 {
| -----------
| |
| these two types are declared with different lifetimes...
6 | x.i
| ^^^ ...but data from `x` flows into `x` here
But this one passes:
struct Foo<'a> {
i: &'a mut i32
}
fn func<'a: 'b, 'b>(x: &'a Foo<'b>) -> &'b i32 {
x.i
}
fn main() {}
This seems a bit counter-intuitive to me. Here, the outer lifetime ('a
) may outlive the inner lifetime ('b
). Why is this not an error?
What is the reason the following code compiles fine, despite both the lifetimes
'a
and'b
being independent of each other?fn func<'a, 'b>(x: &'a Foo<'b>) -> &'b i32 { x.i }
The reason is that they are not independent of each other.
The type &'a Foo<'b>
would be impossible if 'a
outlived 'b
. So, implicitly the Rust borrow-checker is inferring that you must have intended that 'b: 'a
('b
outlives 'a
). So the code above is semantically the same as this:
fn func<'a, 'b: 'a>(x: &'a Foo<'b>) -> &'b i32 {
x.i
}
If I make the reference
i
inFoo
mutable, it gives the following error.5 | fn func<'a, 'b>(x: &'a Foo<'b>) -> &'b i32 { | ----------- ------- | | | this parameter and the return type are declared with different lifetimes... 6 | x.i | ^^^ ...but data from `x` is returned here
What is the reason it gives the above error?
When you pass around an immutable reference, it gets copied. In the above example, that means that the &'b i32
can be moved around by itself, and its liveness is not tied to where you got it from. This copied reference always points back to the original address of the data. And that's why the first example works - even if x
was dropped, the original reference would still be valid.
When you pass around a mutable reference, it gets moved. A consequence of that is this case is that the reference is still "through" the variable x
. If this wasn't the case, x
's mutable reference to the contents of Foo
could be live at the same time as this new immutable reference - which is not allowed. So, in this case the reference cannot outlive 'a
- or put another way: 'a: 'b
.
But didn't we already say 'b: 'a
? The only conclusion here is that 'a
and 'b
must be the same lifetime, which is what your previous error message was demanding.
What is the reason it gives the above error?. Does it consider it's ownership over mutable reference and it sees that something (from
Foo
) is being taken out (with an independent lifetime), which is not possible, hence the error ?
The mutable borrow indeed cannot be moved out of Foo since mutable borrows are not Copy
. It is implicitly immutably reborrowed:
fn func <'a, 'b> (x:&'a Foo<'b>) -> &'b i32 {
&*x.i
}
However, this is not the source of the problem. First, here is a summary of the four versions of func
(struct Foo
has no relevance in my explanation):
version 1 (compiles):
fn func<'a, 'b>(x: &'a &'b i32) -> &'b i32 {
x
}
version 2 (fails to compile):
fn func<'a, 'b>(x: &'a &'b mut i32) -> &'b i32 {
x
}
version 3 (fails to compile):
fn func<'a, 'b: 'a>(x: &'a &'b mut i32) -> &'b i32 {
x
}
version 4 (compiles):
fn func<'a: 'b, 'b>(x: &'a &'b mut i32) -> &'b i32 {
x
}
Version 2 and 3 fail, because they violate the no-aliasing rule which forbids to have a mutable reference and an immutable reference to a resource at the same time. In both versions 'b
may strictly outlive 'a
. Therefore, &'b mut i32
and &'b i32
could coexist. Version 1 compiles, because the aliasing rules allow multiple immutable references to a resource at the same time. Therefore, &'b i32
may legally coexist with anothor &'b i32
.
At first sight, it looks like version 4 should fail since there are again a mutable borrow and an immutable borrow of the same lifetime. The difference to version 2 and 3 is that this time 'a
lives at least as long as 'b
due to the requirement 'a: 'b
, which implies that 'b
may not strictly outlive 'a
. As long as lifetime 'a
lasts the referenced i32
cannot be mutably borrowed a second time (mutable references are not Copy
) - the i32
is already mutably borrowed for the func
call.
Here is an example demonstrating, how version 2 and 3 could lead to undefined behavior:
fn func<'a, 'b: 'a>(x: &'a &'b mut String) -> &'b str {
unsafe { std::mem::transmute(&**x as &str) } // force compilation
}
fn main() {
let mut s = String::from("s");
let mutref_s = &mut s;
let ref_s = {
let ref_mutref_s = &mutref_s;
func(ref_mutref_s)
};
// use the mutable reference to invalidate the string slice
mutref_s.clear();
mutref_s.shrink_to_fit();
// use the invalidated string slice
println!("{:?}", ref_s);
}
Swapping version 3 with version 4 shows how the in this case still active outer immutable borrow prevents the second mutable borrow. Lifetime 'a
on the outer immutable borrow is forced by the new requirement 'a: 'b
to be expanded to be equal to lifetime 'b
:
error[E0502]: cannot borrow `*mutref_s` as mutable because `mutref_s` is also borrowed as immutable
--> src/main.rs:20:5
|
17 | let ref_mutref_s = &mutref_s;
| -------- immutable borrow occurs here
...
20 | mutref_s.clear();
| ^^^^^^^^ mutable borrow occurs here
...
23 | }
| - immutable borrow ends here
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With