Skip to content

Commit

Permalink
Rewrite lifetimes section
Browse files Browse the repository at this point in the history
  • Loading branch information
mdinger committed Jun 30, 2015
1 parent ea11d6f commit 9c3cc4c
Show file tree
Hide file tree
Showing 21 changed files with 317 additions and 203 deletions.
19 changes: 0 additions & 19 deletions examples/scope/lifetime/borrow/borrow.rs

This file was deleted.

53 changes: 0 additions & 53 deletions examples/scope/lifetime/borrow/input.md

This file was deleted.

23 changes: 23 additions & 0 deletions examples/scope/lifetime/elision/elision.rs
@@ -0,0 +1,23 @@
// These two functions have essentially identical signatures
// because the compiler implicitly adds the lifetimes to
// the first.
fn elide_input(x: &i32) {
println!("`elide_input`: {}", x)
}
fn annotated_input<'a>(x: &'a i32) {
println!("`annotated_input`: {}", x)
}

// Similarly, lifetimes are added implicitly to the first.
fn elide_pass(x: &i32) -> &i32 { x }
fn annotated_pass<'a>(x: &'a i32) -> &'a i32 { x }

fn main() {
let x = 3;

elide_input(&x);
annotated_input(&x);

println!("`elide_pass`: {}", elide_pass(&x));
println!("`annotated_pass`: {}", annotated_pass(&x));
}
14 changes: 14 additions & 0 deletions examples/scope/lifetime/elision/input.md
@@ -0,0 +1,14 @@
Some lifetime patterns are overwelmingly common and so they may be elide
(dropped) and the borrow checker will implicitly add them. Elision exists
solely because these patterns are common; saving typing and easing legibility.

This section is brief and not comprehensive. See [lifetime elision][elision]
in the book for a more comprehensive treatment.

{elision.play}

### See also:

[elision][elision]

[elision]: http://doc.rust-lang.org/book/lifetimes.html#lifetime-elision
44 changes: 27 additions & 17 deletions examples/scope/lifetime/explicit/explicit.rs
@@ -1,24 +1,34 @@
struct Book {
// `String` is a heap allocated string
title: String,
author: String,
year: i32,
// `print_refs` takes two references to `i32` which have different
// lifetimes `'a` and `'b`. These two lifetimes must both outlive
// the function `print_refs`.
fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) {
println!("x is {} and y is {}", x, y);

// Suppose this `drop` worked. Then `print_refs` would outlive
// `x` and `four` from `main()` would refer to erased data.
// These two things go hand in hand and so both are banned.
//
//drop(*x);
// ERROR: cannot move out of borrowed content
}

fn get_title<'a>(book: &'a Book) -> &'a str {
&book.title
// `failed_borrow` takes no references and returns nothing but has
// a single lifetime `'a` which must outlive the function.
fn failed_borrow<'a>() {
let _x = 12;

// Attempting to use the lifetime `'a` as an explicit type
// annotation inside the function will fail because the
// lifetime `'a` doesn't match the lifetime that `y` has.
// `y` starts inside `failed_borrow` and so it is smaller.
//
//let y: &'a i32 = &_x;
// ERROR: `_x` does not live long enough
}

fn main() {
let geb = Book {
// construct a `String` from a reference to a string (`&'static str`)
// by copying of the data
author: "Douglas Hofstadter".to_string(),
title: "Godel, Escher, Bach".to_string(),
year: 1979,
};

let title: &str = get_title(&geb);
let (four, nine) = (4, 9);

println!("I just read {}", title);
print_refs(&four, &nine);
failed_borrow();
}
36 changes: 25 additions & 11 deletions examples/scope/lifetime/explicit/input.md
@@ -1,16 +1,30 @@
When writing functions that return references, lifetimes must be explicitly
annotated. These functions are generic and we must tell the compiler what is
the relationship between the lifetimes of the objects that appear in the
arguments and the output.
The borrow checker utilizes explicit lifetime annotation to reason about
how long references should be valid. Failure to annotate lifetimes[^1] is akin
to banning the borrow checker from validating borrows and so accordingly,
annotation is mandatory.

Let's illustrate with an example: we want a function that returns a reference
to the title field of a Book struct. The most generic function that we could
write would look like this:
Since lifetimes *currently* have no explicit type or name associated with them,
usage will require generics (similar to [closures][anonymity]). Somewhat
peculiarly, lifetime annotation has a second additional meaning. `foo<'a, 'b>`
states:

1. `'a` and `'b` will represent names for lifetimes with non-specifiable
(generic) types.
2. The lifetime of `foo` may not exceed either lifetimes `'a` or `'b`.

Explicit annotation of a type has the form: `&'a T` where `'a` has already
been introduced.

{explicit.play}

The compiler can't tell how `'a` and `'b` are related, so we must supply this
information. The answer here is that `'a = 'b`, the reason is that the title
field will be destroyed when the book gets destroyed (same way with the
creation time), therefore the title field has the same lifetime as the book.
[^1]: [elision][elision] implicitly annotates lifetimes and so is different.

### See also:

[generics][generics] and [closures][closures]


[anonymity]: /fn/closures/anonymity.html
[closures]: /fn/closures.html
[elision]: http://doc.rust-lang.org/nightly/book/lifetimes.html#lifetime-elision
[generics]: /generics.html
80 changes: 38 additions & 42 deletions examples/scope/lifetime/fn/fn.rs
@@ -1,50 +1,46 @@
#[derive(Debug)]
struct Triplet {
one: i32,
two: i32,
three: i32,
// One input reference with lifetime `'a` which must live
// longer than the function. This restricts the function
// from ever being able to consume the input.
fn print_one<'a>(x: &'a i32) {
println!("`print_one`: x is {}", x);
}

impl Triplet {
// First attempt: No explicit lifetimes
// The compiler infers that the field and the struct have the same lifetime
fn mut_one(&mut self) -> &mut i32 {
&mut self.one
}

// Second attempt: We explicitly annotate the lifetimes on all the
// references
// Error! The compiler doesn't know what is the relationship between the
// lifetime `structure` and the lifetime `field`
//fn mut_two<'structure, 'field>(&'structure mut self) -> &'field mut i32 {
//&mut self.two
//}
// TODO ^ Try uncommenting this method

// Third attempt: We think! What is the relationship between the lifetimes?
// Clearly `'field` *can't* outlive `'structure`, because the field will be
// destroyed when the struct gets destroyed
// If the fields get destroyed along with the struct, then that means that
// both the struct and its field have the same lifetime!
// Ok, so we need to tell the compiler that `'structure` = `'field`
// We can use a shorter name for the lifetime, it's common to use a single
// letter lifetime, let's use `'s`, because it's the first letter of
// structure
fn mut_three<'s>(&'s mut self) -> &'s mut i32 {
&mut self.three
}
// Mutable references are possible with lifetimes as well.
fn add_one<'a>(x: &'a mut i32) {
*x += 1;
}

fn main() {
let mut triplet = Triplet { one: 1, two: 2, three: 3 };

println!("Before: {:?}", triplet);
// Multiple elements with different lifetimes. This would
// be equally acceptable if both references had the same
// lifetime `'a`.
fn print_multi<'a, 'b>(x: &'a i32, y: &'b i32) {
println!("`print_multi`: x is {}, y is {}", x, y);
}

*triplet.mut_one() = 0;
println!("After: {:?}", triplet);
// This is invalid. An `i32` would be created, a reference
// would be created, then immediately the data would be
// dropped leaving a reference to invalid data to be returned.
//
// The reason the problem is caught is because of the restriction
// `<'a>` imposes: `'a` must live longer than the function.
//fn invalid_output<'a>() -> &'a i32 { &7 }

// Use mutable reference to modify the original struct
*triplet.mut_three() = 0;
// While returning references without input is banned, returning
// references that have been passed in are perfectly acceptable.
// One restriction is the correct lifetime must be returned.
fn pass_x<'a, 'b>(x: &'a i32, _: &'b i32) -> &'a i32 { x }

println!("After: {:?}", triplet);
fn main() {
let x = 7;
let y = 9;

print_one(&x);
print_multi(&x, &y);

let z = pass_x(&x, &y);
print_one(z);

let mut t = 3;
add_one(&mut t);
print_one(&t);
}
15 changes: 13 additions & 2 deletions examples/scope/lifetime/fn/input.md
@@ -1,4 +1,15 @@
Explicit lifetimes are necessary when functions return references. Our case
study will be returning a reference to one of the fields of a struct.
Functions with lifetimes have a few different valid forms. Ignoring
[elision][elision] for the time being, the rules for function parameters are:

* any reference *must* have an annotated lifetime.
* any reference being returned *must* have the same lifetime as an input.

{fn.play}

### See also:

[functions][fn]


[elision]: http://doc.rust-lang.org/nightly/book/lifetimes.html#lifetime-elision
[fn]: /fn.html
40 changes: 10 additions & 30 deletions examples/scope/lifetime/input.md
@@ -1,33 +1,13 @@
The compiler enforces valid borrowing using its borrow checker. To accomplish
this, it keeps track of the scope of blocks.
A *lifetime* is a construct the compiler (also called the borrow checker)
uses to ensure all borrows are valid. Specifically, the lifetime refers to
the span which starts when the variable is created and ends when the variable
is destroyed. Borrowing (via `&` for example) also creates new lifetimes.

The lifetime of an object starts when the object is created and ends when it
goes out of scope (i.e. it gets destroyed, because of the RAII discipline).
A borrow is valid as long as the borrow ends before (inside) the lender is
destroyed. As you can see below, the lifetime of a variable is directly related
to the scope in which it was created:

A lifetime looks like this: `'burrito`, which reads as: "the lifetime burrito".
{lifetime.play}

All references actually have a type signature of the form `&'a T`, where
`'a` is the lifetime of the *referenced* object. The compiler takes care of
inserting the lifetime part `'a` so we can simply type annotate references with
`&T`.

For example:

```rust
let integer: int = 5;
let ref_to_int: &int = &integer;
```

* `integer` has lifetime `'i` (it could be any other name, like `'foo`)
* `ref_to_int` has lifetime `'r` (references also have lifetimes!)
* `ref_to_int` type signature actually is `&'i int` (the compiler inserts the
`'i` for us)
* The type signature `&'i int` reads as:
* `&`: reference to an
* `int`: integer with
* `'i`: lifetime `i` (`i` is the lifetime of `integer`!)

Because the compiler keeps track of the lifetime of referenced objects in the
type system, it can avoid several memory bugs.

Haven't grokked what a lifetime is yet? Don't dismay! See the next page.
You may have noted that no names or types are assigned to label lifetimes.
This restricts how lifetimes will be able to be used as we will see.
12 changes: 12 additions & 0 deletions examples/scope/lifetime/lifetime.rs
@@ -0,0 +1,12 @@
// Lifetimes are annotated with a lines denoting
// when each variable is created and destroyed:
fn main() {
let i = 3; // Lifetime for `i` starts. ───────┐
// │
{ // │
let borrow = &i; // Borrow starts. ──────┐│
// ││
println!("Borrowed `i`: {}", borrow); // ││
} // Borrow ends. ───────────────────────────┘│
// │
} // Lifetime ends. ────────────────────────────┘

0 comments on commit 9c3cc4c

Please sign in to comment.