1
1
# Lifetimes
2
2
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.
7
15
8
16
Within a function body, Rust generally doesn't let you explicitly name the
9
17
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
23
31
explicitly is * extremely noisy* . All Rust code relies on aggressive inference
24
32
and elision of "obvious" things.
25
33
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:
30
38
31
39
``` rust
32
40
let x = 0 ;
85
93
86
94
Alright, let's look at some of those examples from before:
87
95
88
- ``` rust,ignore
96
+ ``` rust,compile_fail
89
97
fn as_str(data: &u32) -> &str {
90
98
let s = format!("{}", data);
91
99
&s
@@ -169,7 +177,7 @@ our implementation *just a bit*.)
169
177
170
178
How about the other example:
171
179
172
- ``` rust,ignore
180
+ ``` rust,compile_fail
173
181
let mut data = vec![1, 2, 3];
174
182
let x = &data[0];
175
183
data.push(4);
@@ -201,7 +209,7 @@ violate the *second* rule of references.
201
209
202
210
However this is * not at all* how Rust reasons that this program is bad. Rust
203
211
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
205
213
be printed. The signature of ` Index::index ` subsequently demands that the
206
214
reference we take to ` data ` has to survive for ` 'b ` . When we try to call ` push ` ,
207
215
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
213
221
to the compiler. However it does mean that several programs that are totally
214
222
correct with respect to Rust's * true* semantics are rejected because lifetimes
215
223
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