From 698f8c2f01ea549d77d7dc3338a12e04c11057b9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:02:58 +0200 Subject: Adding upstream version 1.64.0+dfsg1. Signed-off-by: Daniel Baumann --- src/doc/nomicon/src/lifetimes.md | 302 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 src/doc/nomicon/src/lifetimes.md (limited to 'src/doc/nomicon/src/lifetimes.md') diff --git a/src/doc/nomicon/src/lifetimes.md b/src/doc/nomicon/src/lifetimes.md new file mode 100644 index 000000000..ef86b7b53 --- /dev/null +++ b/src/doc/nomicon/src/lifetimes.md @@ -0,0 +1,302 @@ +# Lifetimes + +Rust enforces these rules through *lifetimes*. Lifetimes are named +regions of code that a reference must be valid for. Those regions +may be fairly complex, as they correspond to paths of execution +in the program. There may even be holes in these paths of execution, +as it's possible to invalidate a reference as long as it's reinitialized +before it's used again. Types which contain references (or pretend to) +may also be tagged with lifetimes so that Rust can prevent them from +being invalidated as well. + +In most of our examples, the lifetimes will coincide with scopes. This is +because our examples are simple. The more complex cases where they don't +coincide are described below. + +Within a function body, Rust generally doesn't let you explicitly name the +lifetimes involved. This is because it's generally not really necessary +to talk about lifetimes in a local context; Rust has all the information and +can work out everything as optimally as possible. Many anonymous scopes and +temporaries that you would otherwise have to write are often introduced to +make your code Just Work. + +However once you cross the function boundary, you need to start talking about +lifetimes. Lifetimes are denoted with an apostrophe: `'a`, `'static`. To dip +our toes with lifetimes, we're going to pretend that we're actually allowed +to label scopes with lifetimes, and desugar the examples from the start of +this chapter. + +Originally, our examples made use of *aggressive* sugar -- high fructose corn +syrup even -- around scopes and lifetimes, because writing everything out +explicitly is *extremely noisy*. All Rust code relies on aggressive inference +and elision of "obvious" things. + +One particularly interesting piece of sugar is that each `let` statement +implicitly introduces a scope. For the most part, this doesn't really matter. +However it does matter for variables that refer to each other. As a simple +example, let's completely desugar this simple piece of Rust code: + +```rust +let x = 0; +let y = &x; +let z = &y; +``` + +The borrow checker always tries to minimize the extent of a lifetime, so it will +likely desugar to the following: + + +```rust,ignore +// NOTE: `'a: {` and `&'b x` is not valid syntax! +'a: { + let x: i32 = 0; + 'b: { + // lifetime used is 'b because that's good enough. + let y: &'b i32 = &'b x; + 'c: { + // ditto on 'c + let z: &'c &'b i32 = &'c y; + } + } +} +``` + +Wow. That's... awful. Let's all take a moment to thank Rust for making this easier. + +Actually passing references to outer scopes will cause Rust to infer +a larger lifetime: + +```rust +let x = 0; +let z; +let y = &x; +z = y; +``` + + +```rust,ignore +'a: { + let x: i32 = 0; + 'b: { + let z: &'b i32; + 'c: { + // Must use 'b here because the reference to x is + // being passed to the scope 'b. + let y: &'b i32 = &'b x; + z = y; + } + } +} +``` + +## Example: references that outlive referents + +Alright, let's look at some of those examples from before: + +```rust,compile_fail +fn as_str(data: &u32) -> &str { + let s = format!("{}", data); + &s +} +``` + +desugars to: + + +```rust,ignore +fn as_str<'a>(data: &'a u32) -> &'a str { + 'b: { + let s = format!("{}", data); + return &'a s; + } +} +``` + +This signature of `as_str` takes a reference to a u32 with *some* lifetime, and +promises that it can produce a reference to a str that can live *just as long*. +Already we can see why this signature might be trouble. That basically implies +that we're going to find a str somewhere in the scope the reference +to the u32 originated in, or somewhere *even earlier*. That's a bit of a tall +order. + +We then proceed to compute the string `s`, and return a reference to it. Since +the contract of our function says the reference must outlive `'a`, that's the +lifetime we infer for the reference. Unfortunately, `s` was defined in the +scope `'b`, so the only way this is sound is if `'b` contains `'a` -- which is +clearly false since `'a` must contain the function call itself. We have therefore +created a reference whose lifetime outlives its referent, which is *literally* +the first thing we said that references can't do. The compiler rightfully blows +up in our face. + +To make this more clear, we can expand the example: + + +```rust,ignore +fn as_str<'a>(data: &'a u32) -> &'a str { + 'b: { + let s = format!("{}", data); + return &'a s + } +} + +fn main() { + 'c: { + let x: u32 = 0; + 'd: { + // An anonymous scope is introduced because the borrow does not + // need to last for the whole scope x is valid for. The return + // of as_str must find a str somewhere before this function + // call. Obviously not happening. + println!("{}", as_str::<'d>(&'d x)); + } + } +} +``` + +Shoot! + +Of course, the right way to write this function is as follows: + +```rust +fn to_string(data: &u32) -> String { + format!("{}", data) +} +``` + +We must produce an owned value inside the function to return it! The only way +we could have returned an `&'a str` would have been if it was in a field of the +`&'a u32`, which is obviously not the case. + +(Actually we could have also just returned a string literal, which as a global +can be considered to reside at the bottom of the stack; though this limits +our implementation *just a bit*.) + +## Example: aliasing a mutable reference + +How about the other example: + +```rust,compile_fail +let mut data = vec![1, 2, 3]; +let x = &data[0]; +data.push(4); +println!("{}", x); +``` + + +```rust,ignore +'a: { + let mut data: Vec = vec![1, 2, 3]; + 'b: { + // 'b is as big as we need this borrow to be + // (just need to get to `println!`) + let x: &'b i32 = Index::index::<'b>(&'b data, 0); + 'c: { + // Temporary scope because we don't need the + // &mut to last any longer. + Vec::push(&'c mut data, 4); + } + println!("{}", x); + } +} +``` + +The problem here is a bit more subtle and interesting. We want Rust to +reject this program for the following reason: We have a live shared reference `x` +to a descendant of `data` when we try to take a mutable reference to `data` +to `push`. This would create an aliased mutable reference, which would +violate the *second* rule of references. + +However this is *not at all* how Rust reasons that this program is bad. Rust +doesn't understand that `x` is a reference to a subpath of `data`. It doesn't +understand `Vec` at all. What it *does* see is that `x` has to live for `'b` in +order to be printed. The signature of `Index::index` subsequently demands that +the reference we take to `data` has to survive for `'b`. When we try to call +`push`, it then sees us try to make an `&'c mut data`. Rust knows that `'c` is +contained within `'b`, and rejects our program because the `&'b data` must still +be alive! + +Here we see that the lifetime system is much more coarse than the reference +semantics we're actually interested in preserving. For the most part, *that's +totally ok*, because it keeps us from spending all day explaining our program +to the compiler. However it does mean that several programs that are totally +correct with respect to Rust's *true* semantics are rejected because lifetimes +are too dumb. + +## The area covered by a lifetime + +A reference (sometimes called a *borrow*) is *alive* from the place it is +created to its last use. The borrowed value needs to outlive only borrows that +are alive. This looks simple, but there are a few subtleties. + +The following snippet compiles, because after printing `x`, it is no longer +needed, so it doesn't matter if it is dangling or aliased (even though the +variable `x` *technically* exists to the very end of the scope). + +```rust +let mut data = vec![1, 2, 3]; +let x = &data[0]; +println!("{}", x); +// This is OK, x is no longer needed +data.push(4); +``` + +However, if the value has a destructor, the destructor is run at the end of the +scope. And running the destructor is considered a use ‒ obviously the last one. +So, this will *not* compile. + +```rust,compile_fail +#[derive(Debug)] +struct X<'a>(&'a i32); + +impl Drop for X<'_> { + fn drop(&mut self) {} +} + +let mut data = vec![1, 2, 3]; +let x = X(&data[0]); +println!("{:?}", x); +data.push(4); +// Here, the destructor is run and therefore this'll fail to compile. +``` + +One way to convince the compiler that `x` is no longer valid is by using `drop(x)` before `data.push(4)`. + +Furthermore, there might be multiple possible last uses of the borrow, for +example in each branch of a condition. + +```rust +# fn some_condition() -> bool { true } +let mut data = vec![1, 2, 3]; +let x = &data[0]; + +if some_condition() { + println!("{}", x); // This is the last use of `x` in this branch + data.push(4); // So we can push here +} else { + // There's no use of `x` in here, so effectively the last use is the + // creation of x at the top of the example. + data.push(5); +} +``` + +And a lifetime can have a pause in it. Or you might look at it as two distinct +borrows just being tied to the same local variable. This often happens around +loops (writing a new value of a variable at the end of the loop and using it for +the last time at the top of the next iteration). + +```rust +let mut data = vec![1, 2, 3]; +// This mut allows us to change where the reference points to +let mut x = &data[0]; + +println!("{}", x); // Last use of this borrow +data.push(4); +x = &data[3]; // We start a new borrow here +println!("{}", x); +``` + +Historically, Rust kept the borrow alive until the end of scope, so these +examples might fail to compile with older compilers. Also, there are still some +corner cases where Rust fails to properly shorten the live part of the borrow +and fails to compile even when it looks like it should. These'll be solved over +time. -- cgit v1.2.3