The concept of rust lifetime - rust

Question
I usually use c++ lang, and recently I'm learning rust lang but now confusing the concept of lifetime.
My understanding for lifetime is as follows. Is this correct?
Lifetime is an attribute of instance.
Lifetime represents the valid scope of instance.
Background of above question
The following code is a sample code at here.
{
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
The document said that 'a is a lifetime, and 'b is also a lifetime.
but if my understanding is correct, 'a is not lifetime, just scope of symbol r... Is `a really lifetime?
P.S.
There are two things named "a lifetime": value's lifetime, and the lifetime attached to a reference.
Thank you! Maybe I understood a little bit more than before...

A lifetime is not so much an attribute of an instance, it's more the opposite way around; when an instance is borrowed (a reference is taken), it is borrowed for a specific lifetime. The borrow checker will try to minimise the lifetime of the borrow so that it can be as permissive as possible whilst ensuring code is still safe.
Lifetimes are your way of communicating how long you need a reference to last to the compiler, and given that information, the compiler will check that nothing violates that rule (by holding the reference for longer), and that the reference is available for at least as long as you require it. This can be thought of in much the same way that a compiler can check type to ensure you don't assign a float to an integer.
Lifetimes are also independent of scope. Rust now has non-lexical lifetimes (https://github.com/rust-lang/rfcs/pull/2094 — see What are non-lexical lifetimes? for more detailed explanation), meaning that within a given scope, the borrow checker is capable of determining that borrows have shorter lifetimes than the containing scope.

Related

A value that is no longer borrowed causes a "does not live long enough" error

This program cannot be compiled.
struct F<'a>(Box<dyn Fn() + 'a>);
fn main() {
let mut v = vec![]; // Vec<F>
let s = String::from("foo");
let f = F(Box::new(|| println!("{:?}", s)));
v.push(f);
drop(v);
}
error[E0597]: `s` does not live long enough
--> src/main.rs:7:44
|
7 | let f = F(Box::new(|| println!("{:?}", s)));
| -- ^ borrowed value does not live long enough
| |
| value captured here
...
11 | }
| -
| |
| `s` dropped here while still borrowed
| borrow might be used here, when `v` is dropped and runs the `Drop` code for type `Vec`
|
= note: values in a scope are dropped in the opposite order they are defined
When s is dropped(line 11), v is already dropped, so s is not borrowed.
But the compiler said that s was still borrowed. why?
This is due to the consideration that a panic could happen as a result of any function call, since there is no decoration on functions indicating whether they might panic.
When a panic occurs, the stack unwinds and the drop code for each (initialized) variable is run in the opposite of their declaration order (s is dropped first, and then v) But v has the type Vec<F<'a>> where 'a is the lifetime of s, and F implements Drop, which means that s cannot be dropped before v because the compiler can't guarantee that the drop code for F won't access s.
The compiler cannot tell that there isn't actually a memory safety issue here (if push panics, the vector doesn't reference s through the closure). All it knows is that the type of v must live at least as long as s; whether v actually contains a reference to s is immaterial.
To fix this, just swap the order v and s are declared in, which will guarantee that v is dropped before s.
But why does F implement Drop in the first place?
Note that the problem goes away if you remove the Fn() trait object and push the closure directly (e.g. without dyn). This case is different because the compiler knows that the closure doesn't implement Drop -- the closure didn't move-capture any values that implement Drop. Therefore, the compiler knows that s will not be accessed by v's drop code.
By comparison, trait objects always have a vtable slot for Drop::drop, and so the compiler must pessimistically assume that every trait object could have a Drop implementation. This means that when the Vec and Box are destroyed, the compiler emits code to call the trait object's drop code, and based on the information the compiler has, that can result in an access to s since the F value captures the lifetime of s.
This is one of the pitfalls about type erasure through trait objects: the trait object is opaque to the compiler and it can no longer verify that s won't be used by a Drop implementation of a boxed closure after s is dropped. If an owned trait object captures a lifetime, the compiler has to ensure that the captured lifetime does not end before the trait object is dropped.
The above is actually a somewhat simplified explanation. Rust's drop-checker is a bit more complex than this; it's okay if F auto-implements Drop so long as the drop-checker determines that the lifetime 'a doesn't get used. Because of the trait object, this can't be guaranteed. However, this code can compile with a Box holding a non-dyn closure as the drop-checker determines that the captured lifetime isn't used when dropping the box.

How can we coerce a mutable reference into an immutable when we can never have both at the same time?

I don't understand how the third case of Deref coercion interacting with mutability is implemented:
From &mut T to &U when T: Deref<Target=U>
It states (emphasis mine):
The third case is trickier: Rust will also coerce a mutable reference
to an immutable one. But the reverse is not possible: immutable
references will never coerce to mutable references. Because of the
borrowing rules, if you have a mutable reference, that mutable
reference must be the only reference to that data (otherwise, the
program wouldn’t compile). Converting one mutable reference to one
immutable reference will never break the borrowing rules. Converting
an immutable reference to a mutable reference would require that the
initial immutable reference is the only immutable reference to that
data, but the borrowing rules don’t guarantee that. Therefore, Rust
can’t make the assumption that converting an immutable reference to a
mutable reference is possible.
The borrowing rules state:
At any given time, you can have either one mutable reference or any
number of immutable references.
How can we coerce a mutable reference into an immutable when we can never have both at the same time? Is there an example? Does this have something to do with liveness?
This is one of those subtleties that's easy to lose when trying to write an informal, readable explanation of the rules. I'll try to explain it as I understand it — but note that I am no expert on the exact borrow rules; I just have a working model which I've explained to myself in my own way.
“Converting a mutable reference to an immutable reference” is not quite what is going on here. Rather, you are borrowing (or “reborrowing”) the mutable reference. This follows the same rules as an owned object:
Given any T you can borrow it to obtain an &'a T, where 'a is a lifetime starting at the use of the & operator.
If we let the type variable T be &'b mut U ('b being whatever lifetime the mutable reference started with) then we substitute it in and obtain
Given any &'b mut U you can borrow it to obtain an &'a &'b mut U, where 'a is a lifetime starting at the use of the & operator.
(In reality we don't always construct this double reference — the very rule you're asking for bypasses it — but I'm using it as a tool to explain what's going on.)
In general, when you borrow an owned T, you can't mutate the T directly until the borrow ends. The exact same principle applies to reborrowing a reference: if you have an &'b mut U and you borrow it, you can't mutate the &'b mut U until the borrow ends. Thus, for the lifetime 'a of the immutable reference, the mutable reference cannot be used to mutate the U. Mutable aliasing is prohibited.
The text “At any given time, you can have either one mutable reference or any number of immutable references.” should be understood as reading “…references to the same value” — a reference is not the same value as its referent. If you write
let x = 1;
let b = &mut x;
let a = &b; // or &*b, or coercion — all produce the same lifetimes
then x is borrowed by b, so can only be mutated through b, and b is borrowed immutably by a, so neither b nor x can actually be used to mutate anything until a is dropped.
The reason I say that this is reborrowing and not converting is because a has a lifetime that is separate from b's.

Unreasonable "cannot borrow `a` as immutable because it is also borrowed as mutable"?

I have seen cannot borrow as immutable because it is also borrowed as mutable and my question is not a duplicate, since my code has Non-Lexical Lifetimes enabled.
I'm wondering if there is a fundamental reason why the following code:
fn f1(a: &u32) {
print!("{:?}", a);
}
fn main() {
let mut a = 3;
let b = &mut a;
f1(&a);
*b += 1;
print!("{:?}", b);
}
must result in the following error:
error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable
--> src/bin/client/main.rs:91:6
|
90 | let b = &mut a;
| ------ mutable borrow occurs here
91 | f1(&a);
| ^^ immutable borrow occurs here
92 | *b += 1;
| ------- mutable borrow later used here
Now, I know that on the line f1(&a), we'll have one mutable reference (b) and one immutable reference (&a), and according to these rules this can't happen. But having 1 mutable and 1 immutable reference can only cause a problem if their usages are interleaved, right? That is, in theory, shouldn't Rust be able to observe that b is not used within &a's existence, and thus accept this program?
Is this just a limitation of the compiler? Or am I overlooking some other memory danger here?
That is, in theory, shouldn't Rust be able to observe that b is not used within &a's existence, and thus accept this program?
Maybe, though it's possible that there are edge cases where this would be a problem. I would expect optimisations to be an issue here e.g. eventually Rust will be able to finally tag &mut as noalias without LLVMs immediately miscompiling things, and in that case your code would be UB if it were allowed.
Is this just a limitation of the compiler?
In this case no, it's literally a limitation of the language specification. There are situations which are limitations of the compiler like loop mutations, but here you're trying to do something the language's rules explicitely and specifically forbid.
Even polonius will not change that.

Passing local lifetime to satisfy trait

I have a generic function creating a local object and taking a trait specifying what to do with that object. The trait takes the reference to the object and holds it for it's lifetime (to avoid passing it again and again to every function call). It dies before the
fn do_stuff<'a, T>()
where T : BigBorrower<'a>
{
let borrowee = Borrowed{ data : 1 };
{
let _borrowee = T::new(&borrowee);
}
}
This is the function call. Because the lifetime for trait has to be specified in function declaraion, it makes the compiler think the lifetime extends lifetime of _borrowee.
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a445fb4ab7befefbadd3bdb8fb43c86a
|
24 | fn do_stuff<'a, T>()
| -- lifetime `'a` defined here
...
29 | let _borrowee = T::new(&borrowee);
| -------^^^^^^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `borrowee` is borrowed for `'a`
30 | }
31 | }
| - `borrowee` dropped here while still borrowed
You've just hit one of the issues with lifetimes and the compiler. Once you realize why it happens, it makes sense.
Your method call enforces a lifetime 'a for the generic type you're providing. This means, amongst other things, that this lifetime needs to be respected and that all objects are required to live for that long. In practice, when you are doing that, the lifetime is that of the function call.
By passing T::new() a reference to a local variable, you are forcing the compiler to pick a lifetime that is inferior to 'a (since it will not outlive the function call), and thus, you are going against your own requirements.
Typically, the way you solve this is to split your do_stuff<'a, T> into two, like on this playground sample. This makes the lifetime check palatable by the compiler, seeing as the life expectancy of that reference is guaranteed to be longer than that of the function being called.
Do note that I renamed your method new in the trait and implementations to borrow, as that's closer to what it is.

How to interpret immutable references to mutable types in Rust?

It seems that I cannot mutate anything if there is any immutable reference in my chain of dereferencing. A sample:
fn main() {
let mut x = 42;
let y: &mut i32 = &mut x; // first layer
let z: &&mut i32 = &y; // second layer
**z = 100; // Attempt to change `x`, gives compiler error.
println!("Value is: {}", z);
}
I'm getting the compiler error:
error[E0594]: cannot assign to `**z` which is behind a `&` reference
--> src/main.rs:5:5
|
4 | let z: &&mut i32 = &y; // second layer
| -- help: consider changing this to be a mutable reference: `&mut y`
5 | **z = 100; // Attempt to change `x`, gives compiler error.
| ^^^^^^^^^ `z` is a `&` reference, so the data it refers to cannot be written
In some way, this makes sense, as otherwise the compiler would not be able to prevent having multiple mutable access paths to the same variable.
However, when looking at the types, the semantics seem to be counter-intuitive:
Variable y has type &mut i32, or in plain English "A mutable reference to an integer".
Variable z has type &&mut i32, or in plain English "An immutable reference to a mutable reference to an integer".
By dereferencing z once (i.e. *z) I will get something of type &mut i32, i.e. something of the same type as y. However, dereferencing this again (i.e. **z) gets me something of type i32, but I am not allowed to mutate that integer.
In essence, the types of references in some sense lie to me, as they don't actually do what they claim they do. How should I read types of references properly in this case, or how else can I restore faith in that concept?
Testing with this sample:
fn main() {
let mut x = 42;
let y: &mut i32 = &mut x; // first layer
let m: &&mut i32 = &y; // second layer
let z: &&&mut i32 = &m; // third layer
compiler_builtin_deref_first_layer(*z);
}
fn compiler_builtin_deref_first_layer(v: &&mut i32) {
compiler_builtin_deref_second_layer(*v);
}
fn compiler_builtin_deref_second_layer(w: &mut i32) {
println!("Value is: {}", w);
}
The parameter types of those last two functions are correct. If I change any of those, the compiler will complain about mismatched types. However, if I compile the example as-is, I get this error:
error[E0596]: cannot borrow `**v` as mutable, as it is behind a `&` reference
Somehow, the call to compiler_builtin_deref_first_layer seems to be okay, but the call to compiler_builtin_deref_second_layer isn't. The compiler error talks about **v, but I only see a *v.
In essence, the types of references in some sense lie to me, as they don't actually do what they claim they do. How should I read types of references properly in this case, or how else can I restore faith in that concept?
The right way to read references in Rust is as permissions.
Ownership of an object, when it's not borrowed, gives you permission to do whatever you want to the object; create it, destroy it, move it from one place to another. You are the owner, you can do what you want, you control the life of that object.
A mutable reference borrows the object from the owner. While the mutable reference is alive, it grants exclusive access to the object. No one else can read, write, or do anything else to the object. A mutable reference could also be called an exclusive reference, or exclusive borrow. You have to return control of the object back to the original owner, but in the meantime, you get to do whatever you want with it.
An immutable reference, or shared borrow, means you get to access it at the same time as others. Because of that, you can only read it, and no one can modify it, or there would be undefined results based on the exact order that the actions happened in.
Both mutable (or exclusive) references and immutable (or shared) references can be made to owned objects, but that doesn't mean that you own the object when you're referring to it through the reference. What you can do with an object is constrained by what kind of reference you're reaching it through.
So don't think of an &&mut T reference as "an immutable reference to a mutable reference to T", and then think "well, I can't mutate the outer reference, but I should be able to mutate the inner reference."
Instead, think of it as "Someone owns a T. They've given out exclusive access, so right now there's someone who has the right to modify the T. But in the meantime, that person has given out shared access to the &mut T, which means they've promised to not mutate it for a period of time, and all of the users can use the shared reference to &mut T, including dereferencing to the underlying T but only for things which you can normally do with a shared reference, which means reading but not writing."
The final thing to keep in mind is that the mutable or immutable part aren't actually the fundamental difference between the references. It's really the exclusive vs. shared part that are. In Rust, you can modify something through a shared reference, as long as there is some kind of inner protection mechanism that ensures that only one person does so at a time. There are multiple ways of doing that, such as Cell, RefCell, or Mutex.
So what &T and &mut T provide isn't really immutable or mutable access, though they are named as such because that's the default level of access they provide at the language level in the absence of any library features. But what they really provide is shared or exclusive access, and then methods on data types can provide different functionality to callers depending on whether they take an owned value, an exclusive reference, or a shared reference.
So think of references as permissions; and it's the reference that you reach something through that determines what you are allowed to do with it. And when you have ownership or an exclusive reference, giving out an exclusive or shared reference temporarily prevents you from mutably accessing the object while those borrowed references are still alive.

Resources