diff --git a/book.toml b/book.toml
index 948de554297e..825142ce8e0d 100644
--- a/book.toml
+++ b/book.toml
@@ -198,6 +198,7 @@ use-boolean-and = true
"generics/trait-objects.html" = "../traits/trait-objects.html"
"hello-world/basic-syntax/functions-interlude.html" = "../../control-flow-basics/functions.html"
"hello-world/hello-world.html" = "../types-and-values/hello-world.html"
+"lifetimes/lifetime-annotations.html" = "../lifetimes.html"
"memory-management/manual.html" = "../memory-management/approaches.html"
"memory-management/rust.html" = "../memory-management/ownership.html"
"memory-management/scope-based.html" = "../memory-management/approaches.html"
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index f8941f81cd1a..d25e9366a669 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -169,7 +169,11 @@
- [Exercise: Health Statistics](borrowing/exercise.md)
- [Solution](borrowing/solution.md)
- [Lifetimes](lifetimes.md)
- - [Lifetime Annotations](lifetimes/lifetime-annotations.md)
+ - [Borrowing and Functions](lifetimes/simple-borrows.md)
+ - [Returning Borrows](lifetimes/returning-borrows.md)
+ - [Multiple Borrows](lifetimes/multiple-borrows.md)
+ - [Borrow Both](lifetimes/borrow-both.md)
+ - [Borrow One](lifetimes/borrow-one.md)
- [Lifetime Elision](lifetimes/lifetime-elision.md)
- [Lifetimes in Data Structures](lifetimes/struct-lifetimes.md)
- [Exercise: Protobuf Parsing](lifetimes/exercise.md)
diff --git a/src/lifetimes/borrow-both.md b/src/lifetimes/borrow-both.md
new file mode 100644
index 000000000000..4191f0886e96
--- /dev/null
+++ b/src/lifetimes/borrow-both.md
@@ -0,0 +1,46 @@
+---
+minutes: 5
+---
+
+# Borrow Both
+
+In this case, we have a function where either `a` or `b` may be returned. In
+this case we use the lifetime annotations to tell the compiler that both borrows
+may flow into the return value.
+
+```rust
+fn pick<'a>(c: bool, a: &'a i32, b: &'a i32) -> &'a i32 {
+ if c { a } else { b }
+}
+
+fn main() {
+ let mut a = 5;
+ let mut b = 10;
+
+ let r = pick(true, &a, &b);
+
+ // Which one is still borrowed?
+ // Should either mutation be allowed?
+ // a += 7;
+ // b += 7;
+
+ dbg!(r);
+}
+```
+
+
+
+- The `pick` function will return either `a` or `b` depending on the value of
+ `c`, which means we can't know at compile time which one will be returned.
+
+- To express this to the compiler, we use the same lifetime for both `a` and
+ `b`, along with the return type. This means that the returned reference will
+ borrow BOTH `a` and `b`!
+
+- Uncomment both of the commented lines and show that `r` is borrowing both `a`
+ and `b`, even though at runtime it will only point to one of them.
+
+- Change the first argument to `pick` to show that the result is the same
+ regardless of if `a` or `b` is returned.
+
+
diff --git a/src/lifetimes/borrow-one.md b/src/lifetimes/borrow-one.md
new file mode 100644
index 000000000000..ae9fe679821c
--- /dev/null
+++ b/src/lifetimes/borrow-one.md
@@ -0,0 +1,77 @@
+---
+minutes: 5
+---
+
+# Borrow One
+
+In this example `find_nearest` takes in multiple borrows but returns only one of
+them. The lifetime annotations explicitly tie the returned borrow to the
+corresponding argument borrow.
+
+```rust,editable
+#[derive(Debug)]
+struct Point(i32, i32);
+
+/// Searches `points` for the point closest to `query`.
+/// Assumes there's at least one point in `points`.
+fn find_nearest<'a>(points: &'a [Point], query: &Point) -> &'a Point {
+ fn cab_distance(p1: &Point, p2: &Point) -> i32 {
+ (p1.0 - p2.0).abs() + (p1.1 - p2.1).abs()
+ }
+
+ let mut nearest = None;
+ for p in points {
+ if let Some((_, nearest_dist)) = nearest {
+ let dist = cab_distance(p, query);
+ if dist < nearest_dist {
+ nearest = Some((p, dist));
+ }
+ } else {
+ nearest = Some((p, cab_distance(p, query)));
+ };
+ }
+
+ nearest.map(|(p, _)| p).unwrap()
+ // query // What happens if we do this instead?
+}
+
+fn main() {
+ let points = &[Point(1, 0), Point(1, 0), Point(-1, 0), Point(0, -1)];
+ let query = Point(0, 2);
+ let nearest = find_nearest(points, &query);
+
+ // `query` isn't borrowed at this point.
+ drop(query);
+
+ dbg!(nearest);
+}
+```
+
+
+
+- It may be helpful to collapse the definition of `find_nearest` to put more
+ focus on the signature of the function. The actual logic in the function is
+ somewhat complex and isn't important for the purpose of borrow analysis.
+
+- When we call `find_nearest` the returned reference doesn't borrow `query`, and
+ so we are free to drop it while `nearest` is still active.
+
+- But what happens if we return the wrong borrow? Change the last line of
+ `find_nearest` to return `query` instead. Show the compiler error to the
+ students.
+
+- The first thing we have to do is add a lifetime annotation to `query`. Show
+ students that we can add a second lifetime `'b` to `find_nearest`.
+
+- Show the new error to the students. The borrow checker verifies that the logic
+ in the function body actually returns a reference with the correct lifetime,
+ enforcing that the function adheres to the contract set by the function's
+ signature.
+
+- The "help" note in the error notes that we can add a lifetime bound `'b: 'a`
+ to say that `'b` will live at least as long as `'a`, which would then allow us
+ to return `query`. On the next slide we'll talk about lifetime variance, which
+ is the rule that allows us to return a longer lifetime when a shorter one is
+ expected.
+
+
diff --git a/src/lifetimes/lifetime-annotations.md b/src/lifetimes/lifetime-annotations.md
deleted file mode 100644
index 35b88bc94977..000000000000
--- a/src/lifetimes/lifetime-annotations.md
+++ /dev/null
@@ -1,64 +0,0 @@
----
-minutes: 10
----
-
-# Lifetime Annotations
-
-A reference has a _lifetime_, which must not "outlive" the value it refers to.
-This is verified by the borrow checker.
-
-The lifetime can be implicit - this is what we have seen so far. Lifetimes can
-also be explicit: `&'a Point`, `&'document str`. Lifetimes start with `'` and
-`'a` is a typical default name. Read `&'a Point` as "a borrowed `Point` which is
-valid for at least the lifetime `a`".
-
-Only ownership, not lifetime annotations, control when values are destroyed and
-determine the concrete lifetime of a given value. The borrow checker just
-validates that borrows never extend beyond the concrete lifetime of the value.
-
-Explicit lifetime annotations, like types, are required on function signatures
-(but can be elided in common cases). These provide information for inference at
-callsites and within the function body, helping the borrow checker to do its
-job.
-
-
-
-```rust,editable,compile_fail
-#[derive(Debug)]
-struct Point(i32, i32);
-
-fn left_most(p1: &Point, p2: &Point) -> &Point {
- if p1.0 < p2.0 { p1 } else { p2 }
-}
-
-fn main() {
- let p1 = Point(10, 10);
- let p2 = Point(20, 20);
- let p3 = left_most(&p1, &p2); // What is the lifetime of p3?
- dbg!(p3);
-}
-```
-
-
-
-In this example, the compiler does not know what lifetime to infer for `p3`.
-Looking inside the function body shows that it can only safely assume that
-`p3`'s lifetime is the shorter of `p1` and `p2`. But just like types, Rust
-requires explicit annotations of lifetimes on function arguments and return
-values.
-
-Add `'a` appropriately to `left_most`:
-
-```rust,ignore
-fn left_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point {
-```
-
-This says there is some lifetime `'a` which both `p1` and `p2` outlive, and
-which outlives the return value. The borrow checker verifies this within the
-function body, and uses this information in `main` to determine a lifetime for
-`p3`.
-
-Try dropping `p2` in `main` before printing `p3`.
-
-
diff --git a/src/lifetimes/lifetime-elision.md b/src/lifetimes/lifetime-elision.md
index 539a6475f029..5094724e9c3d 100644
--- a/src/lifetimes/lifetime-elision.md
+++ b/src/lifetimes/lifetime-elision.md
@@ -16,60 +16,29 @@ This is not inference -- it is just a syntactic shorthand.
that lifetime is given to all un-annotated return values.
```rust,editable
-#[derive(Debug)]
-struct Point(i32, i32);
-
-fn cab_distance(p1: &Point, p2: &Point) -> i32 {
- (p1.0 - p2.0).abs() + (p1.1 - p2.1).abs()
+fn only_args(a: &i32, b: &i32) {
+ todo!();
}
-fn find_nearest<'a>(points: &'a [Point], query: &Point) -> Option<&'a Point> {
- let mut nearest = None;
- for p in points {
- if let Some((_, nearest_dist)) = nearest {
- let dist = cab_distance(p, query);
- if dist < nearest_dist {
- nearest = Some((p, dist));
- }
- } else {
- nearest = Some((p, cab_distance(p, query)));
- };
- }
- nearest.map(|(p, _)| p)
+fn identity(a: &i32) -> &i32 {
+ a
}
-fn main() {
- let points = &[Point(1, 0), Point(1, 0), Point(-1, 0), Point(0, -1)];
- let nearest = {
- let query = Point(0, 2);
- find_nearest(points, &query)
- };
- println!("{:?}", nearest);
+struct Foo(i32);
+impl Foo {
+ fn get(&self, other: &i32) -> &i32 {
+ &self.0
+ }
}
```
-In this example, `cab_distance` is trivially elided.
-
-The `nearest` function provides another example of a function with multiple
-references in its arguments that requires explicit annotation. In `main`, the
-return value is allowed to outlive the query.
-
-Try adjusting the signature to "lie" about the lifetimes returned:
-
-```rust,ignore
-fn find_nearest<'a, 'q>(points: &'a [Point], query: &'q Point) -> Option<&'q Point> {
-```
-
-This won't compile, demonstrating that the annotations are checked for validity
-by the compiler. Note that this is not the case for raw pointers (unsafe), and
-this is a common source of errors with unsafe Rust.
+- Walk through applying the lifetime elision rules to each of the example
+ functions. `only_args` is completed by the first rule, `identity` is completed
+ by the second, and `Foo::get` is completed by the third.
-Students may ask when to use lifetimes. Rust borrows _always_ have lifetimes.
-Most of the time, elision and type inference mean these don't need to be written
-out. In more complicated cases, lifetime annotations can help resolve ambiguity.
-Often, especially when prototyping, it's easier to just work with owned data by
-cloning values where necessary.
+- If all lifetimes have not been filled in by applying the three elision rules
+ then you will get a compiler error telling you to add annotations manually.
diff --git a/src/lifetimes/multiple-borrows.md b/src/lifetimes/multiple-borrows.md
new file mode 100644
index 000000000000..8d1e4680f79b
--- /dev/null
+++ b/src/lifetimes/multiple-borrows.md
@@ -0,0 +1,54 @@
+---
+minutes: 5
+---
+
+# Multiple Borrows
+
+But what about when there are multiple borrows passed into a function and one
+being returned?
+
+```rust,editable,ignore
+fn multiple(a: &i32, b: &i32) -> &i32 {
+ todo!("Return either `a` or `b`")
+}
+
+fn main() {
+ let mut a = 5;
+ let mut b = 10;
+
+ let r = multiple(&a, &b);
+
+ // Which one is still borrowed?
+ // Should either mutation be allowed?
+ a += 7;
+ b += 7;
+
+ dbg!(r);
+}
+```
+
+
+
+- This code does not compile right now because it is missing lifetime
+ annotations. Before we get it to compile, use this opportunity to have
+ students to think about which of our argument borrows should be extended by
+ the return value.
+
+- We pass two borrows into `multiple` and one is going to come back out, which
+ means we will need to extend the borrow of one of the argument lifetimes.
+ Which one should be extended? Do we need to see the body of `multiple` to
+ figure this out?
+
+- When borrow checking, the compiler doesn't look at the body of `multiple` to
+ reason about the borrows flowing out, instead it looks only at the signature
+ of the function for borrow analysis.
+
+- In this case there is not enough information to determine if `a` or `b` will
+ be borrowed by the returned reference. Show students the compiler errors and
+ introduce the lifetime syntax:
+
+ ```rust,ignore
+ fn multiple<'a>(a: &'a i32, b: &'a i32) -> &'a i32 { ... }
+ ```
+
+
diff --git a/src/lifetimes/returning-borrows.md b/src/lifetimes/returning-borrows.md
new file mode 100644
index 000000000000..083ca9a4b18e
--- /dev/null
+++ b/src/lifetimes/returning-borrows.md
@@ -0,0 +1,38 @@
+---
+minutes: 5
+---
+
+# Returning Borrows
+
+But we can also have our function return a reference! This means that a borrow
+flows back out of a function:
+
+```rust,editable
+fn identity(x: &i32) -> &i32 {
+ x
+}
+
+fn main() {
+ let mut x = 123;
+
+ let out = identity(&x);
+
+ // x = 5; // 🛠️❌ `x` is still borrowed!
+
+ dbg!(out);
+}
+```
+
+
+
+- Rust functions can return references, meaning that a borrow can flow back out
+ of a function.
+
+- If a function returns a reference (or another kind of borrow), it was likely
+ derived from one of its arguments. This means that the return value of the
+ function will extend the borrow for one or more argument borrows.
+
+- This case is still fairly simple, in that only one borrow is passed into the
+ function, so the returned borrow has to be the same one.
+
+
diff --git a/src/lifetimes/simple-borrows.md b/src/lifetimes/simple-borrows.md
new file mode 100644
index 000000000000..b1f81b634693
--- /dev/null
+++ b/src/lifetimes/simple-borrows.md
@@ -0,0 +1,33 @@
+---
+minutes: 3
+---
+
+# Borrowing with Functions
+
+As part of borrow checking, the compiler needs to reason about how borrows flow
+into and out of functions. In the simplest case borrows last for the duration of
+the function call:
+
+```rust,editable
+fn borrows(x: &i32) {
+ dbg!(x);
+}
+
+fn main() {
+ let mut val = 123;
+
+ // Borrow `val` for the function call.
+ borrows(&val);
+
+ // Borrow has ended and we're free to mutate.
+ val += 5;
+}
+```
+
+
+
+- In this example we borrow `val` for the call to `borrows`. This would limit
+ our ability to mutate `val`, but once the function call returns the borrow has
+ ended and we're free to mutate again.
+
+