Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite lifetimes section #607

Merged
merged 5 commits into from Aug 15, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 elided
(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
53 changes: 35 additions & 18 deletions examples/scope/lifetime/explicit/explicit.rs
@@ -1,24 +1,41 @@
struct Book {
// `String` is a heap allocated string
title: String,
author: String,
year: i32,
}
// This example uses `main()` at the top so it can be read top to
// bottom. `main()` is usually at the bottom.
fn main() {
// Create variables which will be borrowed.
let (four, nine) = (4, 9);

// Borrows (`&`) of both variables are passed into the function.
// If a borrow is truly a borrow, the variable must be returned,
// otherwise a borrow would be transferring ownership. This means
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would make this sentence

When you pass a function a borrow as an argument, the borrow ends at the end of the function. The alternative to passing a borrow would be trasnferring ownership.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this change. As written, I'm trying to leverage the idea that people understand that borrows must always come back. If they don't come back then they can't be borrows; they must be something else. It's the only option. Then, a transfer would make perfect sense.

With your suggestion, I would immediately ask "why must they be returned"? I wouldn't necessarily see any reason they must be returned other than that is what the docs say.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is that you don't return, like

fn foo<'a>(&'a i32)

Does not return anything, but is just fine. You don't need to explicitly return the reference.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not talking about explicit returning. I'm talking about doing something which would preclude the reference from ever being able to return to it's owner. For example, if I tried this:

// Would this be the lifetime drawing?
fn foo<'a>(a: &'a NonCopyType) { // ---|
//                                     |
    let var = *a; // ------------------|

    ...    

    bar(var);
}

Why would it fail? Well, it would force a move but a move can't be done with a borrow. Why not? Because that would make it something other than a borrow. I consider this related to lifetime size. foo() above has lifetime size which must be at least as large as the function.

Why must this lifetime size be larger than the function? Because borrows must always be returnable. The borrow being returned at the end is just a minor point you could learn after this is realized.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@steveklabnik Should I just find you on IRC tomorrow to try and hash out what to do about this section?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe yeah. I"m not sure how much I'll be on. Still thinking about this.

// no matter what, *any* input which is borrowed via function or
// otherwise, *must* still exist after the borrower ceases. In
// other words, *Rule 2* must be true.
print_refs(&four, &nine);

fn get_title<'a>(book: &'a Book) -> &'a str {
&book.title
// Note that there is no input even though the function specifies
// a lifetime. The sizedness of that lifetime will be determined
// by the caller (which is here) from available lifetimes (scopes).
// Any lifetime chosen will then be larger than that of the function.
failed_borrow();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this, I'm finding it pretty confusing.

}

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,
};
// `print_refs` takes two references to `i32` which have different
// lifetimes `'a` and `'b`. These two lifetimes must both be at
// least as long as the function `print_refs`.
fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) {
println!("x is {} and y is {}", x, y);
}

let title: &str = get_title(&geb);
// There are no references to force `'a` to be larger than the
// function yet, `'a` remains larger than the function.
fn failed_borrow<'a>() {
let _x = 12;

println!("I just read {}", title);
// Attempting to use the lifetime `'a` as an explicit type
// annotation inside the function will fail because the
// lifetime of `&_x` is smaller than `y` has. A small lifetime
// cannot be coerced into a larger one.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this makes the example more clear. Maybe we can re-word the description above. I think the problem is that it says "uses a lifetime" but the syntax isn't anywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any suggestions which would make this clearer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought of something

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay cool, give it a shot :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I already changed it. See the new commit: mdinger@0832d4e for what is different

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah! i wonder why github is still showing me this then

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably because the fix is in a different section where you stated you find it confusing. Here was where you said you were less confused.

//
//let y: &'a i32 = &_x;
// ERROR: `_x` does not live long enough
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nikomatsakis This function above was slightly problematic with your suggested explanation since you cannot rely on input borrowing being returned and deduce the lifetime requirements from that.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, this function makes no sense at all -- a random lifetime parameter that is not used anywhere is not particularly useful or meaningful. (Really, it ought to be illegal.)

That said, I don't quite follow how this example is in conflict with the explanation I suggested. I'm not even 100% sure what explanation you're referring to :). I thought I was explaining how to interpret a type like Foo<'a>, which does not appear in this example, right?

But in any case, the existing text is correct: whatever scope 'a corresponds to (and, given that 'a is completely unused, the caller can pick whatever scope they can name), it will be something larger than the current function, and _x is SMALLER than the current function, so this code is in error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this explanation. Anyway, the rules for Foo<'a> as a struct or foo<'a> as a function are basically the same. Rather than try to explain the same thing twice once for structs and once for functions, I tried to use foo<'a, 'b> as a stand-in for both. Maybe it wasn't successful...

Good. If the explanation is okay then I'm okay with it. As for this example being random...it's not really. It's due to trying the obvious thing following an unhelpful error message. Consider this:

fn test() { let x: &'a i32 = &7; }

Error:

<anon>:1:21: 1:23 error: use of undeclared lifetime name `'a` [E0261]
<anon>:1 fn test() { let x: &'a i32 = &7; }

Oh, it needs declaring. Let's try:

fn test<'a>() { let x: &'a i32 = &7; }

Error:

<anon>:1:35: 1:36 error: borrowed value does not live long enough
<anon>:1 fn test<'a>() { let x: &'a i32 = &7; }

Hmm...is manual annotation of lifetimes possible?

38 changes: 27 additions & 11 deletions examples/scope/lifetime/explicit/input.md
@@ -1,16 +1,32 @@
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 annotations 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]). Additionally,
a second meaning will be associated with this lifetime syntax. `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`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this for 'a: 'b, not 'a, 'b'?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure? I've been interpreting foo<'a, 'b> to mean foo <= min('a, 'b). Would you state it means foo <= max('a, 'b) so if 'a > 'b then 'b < foo < 'a would be valid? I don't really remember this being discussed with more than one lifetime...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, i see what you're saying here. i just found the wording confusing. you are correct though...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good. I looked at foo <= max('a, 'b) and was thinking I really don't think it means that but was trying to give you the benefit of the doubt.


Explicit annotation of a type has the form: `&'a T` where `'a` has already
been introduced. The less obvious of the rules is **Rule 2** whose
importance can usually be directly deduced from borrowing rules. Consider the
following example:

{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]: /scope/lifetime/elision.html
[generics]: /generics.html
79 changes: 37 additions & 42 deletions examples/scope/lifetime/fn/fn.rs
@@ -1,50 +1,45 @@
#[derive(Debug)]
struct Triplet {
one: i32,
two: i32,
three: i32,
// One input reference with lifetime `'a` which must live
// at least as long as the function.
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. In this case, it
// would be fine for both to have the same lifetime `'a`, but
// in more complex cases, different lifetimes may be required.
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);
}
16 changes: 14 additions & 2 deletions examples/scope/lifetime/fn/input.md
@@ -1,4 +1,16 @@
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 or
be `static`.

{fn.play}

### See also:

[functions][fn]


[elision]: /scope/lifetime/elision.html
[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, a lifetime starts when
a variable is created and ends when it is destroyed. It can be visualized as
vertical distance but since size is only relevant for subsets and supersets,
scope is considered the more appropriate descriptive term.

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).
Borrowing (via `&` for example) creates new lifetimes. A borrow is valid
as long as the borrow ends before (inside) the lender is destroyed.

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.
22 changes: 22 additions & 0 deletions examples/scope/lifetime/lifetime.rs
@@ -0,0 +1,22 @@
// Lifetimes are annotated with lines denoting when each variable
// is created and destroyed. `i` has the largest lifetime because it's
// scope entirely encloses both `borrow1` and `borrow2`. The sizedness
// of `borrow1` compared with `borrow2` is irrelevant since they are
// disjoint.
fn main() {
let i = 3; // Lifetime for `i` starts. ───────┐
// │
{ // │
let borrow1 = &i; // `borrow1` starts. ──┐│
// ││
println!("borrow1: {}", borrow1); // ││
} // `borrow1 ends. ─────────────────────────┘│
// │
// │
{ // │
let borrow2 = &i; // `borrow2` starts. ──┐│
// ││
println!("borrow2: {}", borrow2); // ││
} // `borrow2` ends. ────────────────────────┘│
// │
} // Lifetime ends. ────────────────────────────┘