Skip to content

Commit c656171

Browse files
authored
Merge pull request #101 from vorner/nll
Lifetimes: Updates to incorporate NLL
2 parents fb29b14 + 0cc1381 commit c656171

File tree

2 files changed

+142
-31
lines changed

2 files changed

+142
-31
lines changed

src/lifetime-mismatch.md

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
Given the following code:
44

5-
```rust,ignore
5+
```rust,edition2018,compile_fail
6+
#[derive(Debug)]
67
struct Foo;
78
89
impl Foo {
@@ -14,25 +15,25 @@ fn main() {
1415
let mut foo = Foo;
1516
let loan = foo.mutate_and_share();
1617
foo.share();
18+
println!("{:?}", loan);
1719
}
1820
```
1921

20-
One might expect it to compile. We call `mutate_and_share`, which mutably borrows
21-
`foo` temporarily, but then returns only a shared reference. Therefore we
22-
would expect `foo.share()` to succeed as `foo` shouldn't be mutably borrowed.
22+
One might expect it to compile. We call `mutate_and_share`, which mutably
23+
borrows `foo` temporarily, but then returns only a shared reference. Therefore
24+
we would expect `foo.share()` to succeed as `foo` shouldn't be mutably borrowed.
2325

2426
However when we try to compile it:
2527

2628
```text
2729
error[E0502]: cannot borrow `foo` as immutable because it is also borrowed as mutable
28-
--> src/lib.rs:11:5
30+
--> src/main.rs:12:5
2931
|
30-
10 | let loan = foo.mutate_and_share();
32+
11 | let loan = foo.mutate_and_share();
3133
| --- mutable borrow occurs here
32-
11 | foo.share();
34+
12 | foo.share();
3335
| ^^^ immutable borrow occurs here
34-
12 | }
35-
| - mutable borrow ends here
36+
13 | println!("{:?}", loan);
3637
```
3738

3839
What happened? Well, we got the exact same reasoning as we did for
@@ -48,30 +49,53 @@ impl Foo {
4849
}
4950
5051
fn main() {
51-
'b: {
52-
let mut foo: Foo = Foo;
53-
'c: {
54-
let loan: &'c Foo = Foo::mutate_and_share::<'c>(&'c mut foo);
55-
'd: {
56-
Foo::share::<'d>(&'d foo);
57-
}
58-
}
52+
'b: {
53+
let mut foo: Foo = Foo;
54+
'c: {
55+
let loan: &'c Foo = Foo::mutate_and_share::<'c>(&'c mut foo);
56+
'd: {
57+
Foo::share::<'d>(&'d foo);
58+
}
59+
println!("{:?}", loan);
60+
}
5961
}
6062
}
6163
```
6264

6365
The lifetime system is forced to extend the `&mut foo` to have lifetime `'c`,
64-
due to the lifetime of `loan` and mutate_and_share's signature. Then when we
66+
due to the lifetime of `loan` and `mutate_and_share`'s signature. Then when we
6567
try to call `share`, and it sees we're trying to alias that `&'c mut foo` and
6668
blows up in our face!
6769

6870
This program is clearly correct according to the reference semantics we actually
6971
care about, but the lifetime system is too coarse-grained to handle that.
7072

7173

72-
TODO: other common problems? SEME regions stuff, mostly?
73-
7474

75+
# Improperly reduced borrows
76+
77+
This currently fails to compile, because Rust doesn't understand that the borrow
78+
is no longer needed and conservatively falls back to using a whole scope for it.
79+
This will eventually get fixed.
80+
81+
```rust,edition2018,compile_fail
82+
# use std::collections::HashMap;
83+
# use std::cmp::Eq;
84+
# use std::hash::Hash;
85+
fn get_default<'m, K, V>(map: &'m mut HashMap<K, V>, key: K) -> &'m mut V
86+
where
87+
K: Clone + Eq + Hash,
88+
V: Default,
89+
{
90+
match map.get_mut(&key) {
91+
Some(value) => value,
92+
None => {
93+
map.insert(key.clone(), V::default());
94+
map.get_mut(&key).unwrap()
95+
}
96+
}
97+
}
98+
```
7599

76100

77101
[ex2]: lifetimes.html#example-aliasing-a-mutable-reference

src/lifetimes.md

Lines changed: 98 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
# Lifetimes
22

3-
Rust enforces these rules through *lifetimes*. Lifetimes are effectively
4-
just names for scopes somewhere in the program. Each reference,
5-
and anything that contains a reference, is tagged with a lifetime specifying
6-
the scope it's valid for.
3+
Rust enforces these rules through *lifetimes*. Lifetimes are named
4+
regions of code that a reference must be valid for. Those regions
5+
may be fairly complex, as they correspond to paths of execution
6+
in the program. There may even be holes in these paths of execution,
7+
as it's possible to invalidate a reference as long as it's reinitialized
8+
before it's used again. Types which contain references (or pretend to)
9+
may also be tagged with lifetimes so that Rust can prevent them from
10+
being invalidated as well.
11+
12+
In most of our examples, the lifetimes will coincide with scopes. This is
13+
because our examples are simple. The more complex cases where they don't
14+
coincide are described below.
715

816
Within a function body, Rust generally doesn't let you explicitly name the
917
lifetimes involved. This is because it's generally not really necessary
@@ -23,10 +31,10 @@ syrup even -- around scopes and lifetimes, because writing everything out
2331
explicitly is *extremely noisy*. All Rust code relies on aggressive inference
2432
and elision of "obvious" things.
2533

26-
One particularly interesting piece of sugar is that each `let` statement implicitly
27-
introduces a scope. For the most part, this doesn't really matter. However it
28-
does matter for variables that refer to each other. As a simple example, let's
29-
completely desugar this simple piece of Rust code:
34+
One particularly interesting piece of sugar is that each `let` statement
35+
implicitly introduces a scope. For the most part, this doesn't really matter.
36+
However it does matter for variables that refer to each other. As a simple
37+
example, let's completely desugar this simple piece of Rust code:
3038

3139
```rust
3240
let x = 0;
@@ -85,7 +93,7 @@ z = y;
8593

8694
Alright, let's look at some of those examples from before:
8795

88-
```rust,ignore
96+
```rust,compile_fail
8997
fn as_str(data: &u32) -> &str {
9098
let s = format!("{}", data);
9199
&s
@@ -169,7 +177,7 @@ our implementation *just a bit*.)
169177

170178
How about the other example:
171179

172-
```rust,ignore
180+
```rust,compile_fail
173181
let mut data = vec![1, 2, 3];
174182
let x = &data[0];
175183
data.push(4);
@@ -201,7 +209,7 @@ violate the *second* rule of references.
201209

202210
However this is *not at all* how Rust reasons that this program is bad. Rust
203211
doesn't understand that `x` is a reference to a subpath of `data`. It doesn't
204-
understand Vec at all. What it *does* see is that `x` has to live for `'b` to
212+
understand `Vec` at all. What it *does* see is that `x` has to live for `'b` to
205213
be printed. The signature of `Index::index` subsequently demands that the
206214
reference we take to `data` has to survive for `'b`. When we try to call `push`,
207215
it then sees us try to make an `&'c mut data`. Rust knows that `'c` is contained
@@ -213,3 +221,82 @@ totally ok*, because it keeps us from spending all day explaining our program
213221
to the compiler. However it does mean that several programs that are totally
214222
correct with respect to Rust's *true* semantics are rejected because lifetimes
215223
are too dumb.
224+
225+
226+
227+
# The area covered by a lifetime
228+
229+
The lifetime (sometimes called a *borrow*) is *alive* from the place it is
230+
created to its last use. The borrowed thing needs to outlive only borrows that
231+
are alive. This looks simple, but there are few subtleties.
232+
233+
The following snippet compiles, because after printing `x`, it is no longer
234+
needed, so it doesn't matter if it is dangling or aliased (even though the
235+
variable `x` *technically* exists to the very end of the scope).
236+
237+
```rust,edition2018
238+
let mut data = vec![1, 2, 3];
239+
let x = &data[0];
240+
println!("{}", x);
241+
// This is OK, x is no longer needed
242+
data.push(4);
243+
```
244+
245+
However, if the value has a destructor, the destructor is run at the end of the
246+
scope. And running the destructor is considered a use ‒ obviously the last one.
247+
So, this will *not* compile.
248+
249+
```rust,edition2018,compile_fail
250+
#[derive(Debug)]
251+
struct X<'a>(&'a i32);
252+
253+
impl Drop for X<'_> {
254+
fn drop(&mut self) {}
255+
}
256+
257+
let mut data = vec![1, 2, 3];
258+
let x = X(&data[0]);
259+
println!("{:?}", x);
260+
data.push(4);
261+
// Here, the destructor is run and therefore this'll fail to compile.
262+
```
263+
264+
Furthermore, there might be multiple possible last uses of the borrow, for
265+
example in each branch of a condition.
266+
267+
```rust,edition2018
268+
# fn some_condition() -> bool { true }
269+
let mut data = vec![1, 2, 3];
270+
let x = &data[0];
271+
272+
if some_condition() {
273+
println!("{}", x); // This is the last use of `x` in this branch
274+
data.push(4); // So we can push here
275+
} else {
276+
// There's no use of `x` in here, so effectively the last use is the
277+
// creation of x at the top of the example.
278+
data.push(5);
279+
}
280+
```
281+
282+
And a lifetime can have a pause in it. Or you might look at it as two distinct
283+
borrows just being tied to the same local variable. This often happens around
284+
loops (writing a new value of a variable at the end of the loop and using it for
285+
the last time at the top of the next iteration).
286+
287+
```rust,edition2018
288+
let mut data = vec![1, 2, 3];
289+
// This mut allows us to change where the reference points to
290+
let mut x = &data[0];
291+
292+
println!("{}", x); // Last use of this borrow
293+
data.push(4);
294+
x = &data[3]; // We start a new borrow here
295+
println!("{}", x);
296+
```
297+
298+
Historically, Rust kept the borrow alive until the end of scope, so these
299+
examples might fail to compile with older compilers. Also, there are still some
300+
corner cases where Rust fails to properly shorten the live part of the borrow
301+
and fails to compile even when it looks like it should. These'll be solved over
302+
time.

0 commit comments

Comments
 (0)