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

Explain motivation behind lifetimes #36755

Merged
merged 1 commit into from
Oct 15, 2016
Merged

Explain motivation behind lifetimes #36755

merged 1 commit into from
Oct 15, 2016

Conversation

Rantanen
Copy link
Contributor

Start the lifetime section with an explanation of the issues that lack of explicit lifetimes cause and how the explicit lifetimes solve these.


I had really hard time figuring out why I would need to care about the explicit reference lifetimes when going through the book at first. With strong background in C++, I'm familiar with the dangling reference problem - but given the section seems to focus more on the lifetime syntax and various ways to define lifetimes on functions and structs, I was unable to understand how they are used to solve the reference problem.

This PR is an attempt at getting the reader to understand what the explicit lifetimes are used for and why they are an awesome thing instead of a bit of syntax that just has to be written.

It's been less than a week that I've been diving into Rust so I'm far from certain about the terminology and technical correctness. I tried mimicking the existing terminology from the lifetimes section, but still no promises on getting it right.

@rust-highfive
Copy link
Collaborator

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @Manishearth (or someone else) soon.

If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes.

Please see the contribution instructions for more information.

Copy link
Contributor

@durka durka left a comment

Choose a reason for hiding this comment

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

Nice expanded example!


```rust
fn foo() {
let r = &0; // Introduce reference: r
Copy link
Contributor

Choose a reason for hiding this comment

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

Just write let r; here. Initializing it to &0 and then re-assigning it later triggers another error because you didn't declare r mutable, which is irrelevant to the point about lifetimes.

fn bar<'a>(x: &'a i32) {
fn foo(id: u32) {
let data = "#1=foo;#2=bar";
let v : &str;
Copy link
Contributor

Choose a reason for hiding this comment

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

style is usually let v: &str;

explicit in the function declaration:

```rust
fn data_value<'a,'b>(data: &'a str, key: &'b str) -> &'a str {
Copy link
Contributor

Choose a reason for hiding this comment

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

minor style nit: <'a, 'b> with space

["Lifetime Elision"][lifetime-elision] below) them in common cases.
Before we get to that, though, let’s break the explicit example down:
["Lifetime Elision"][lifetime-elision] below) them in common cases. Before we
get to that, though, let’s look at a simple example with explicit lifetime:
Copy link
Contributor

Choose a reason for hiding this comment

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

lifetimes

@Eh2406
Copy link
Contributor

Eh2406 commented Sep 26, 2016

"less than a week" and already helping with the docs! Thank you!

@durka
Copy link
Contributor

durka commented Sep 26, 2016

Travis says:

failures:

---- Lifetimes_1 stdout ----
    error[E0106]: missing lifetime specifier
 --> <anon>:2:45
  |
2 |     fn data_value(data: &str, key: &str) -> &str {
  |                                             ^ expected lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `data` or `key`

error: aborting due to previous error(s)

thread 'Lifetimes_1' panicked at 'Box<Any>', src/librustc/session/mod.rs:183
note: Run with `RUST_BACKTRACE=1` for a backtrace.

---- Lifetimes_0 stdout ----
    error: `i` does not live long enough
  --> <anon>:6:14
   |
6  |         r = &i;         // Store reference of i in r
   |              ^ does not live long enough
7  |     }                   // i goes out of scope and is dropped.
   |     - borrowed value only lives until here
...
10 | }
   | - borrowed value needs to live until here

error[E0384]: re-assignment of immutable variable `r`
 --> <anon>:6:9
  |
3 |     let r = &0;         // Introduce reference: r
  |         - first assignment to `r`
...
6 |         r = &i;         // Store reference of i in r
  |         ^^^^^^ re-assignment of immutable variable

error: aborting due to previous error(s)

thread 'Lifetimes_0' panicked at 'Box<Any>', src/librustc/session/mod.rs:183

---- Lifetimes_2 stdout ----
    error[E0269]: not all control paths return a value
 --> <anon>:2:5
  |
2 |     fn data_value<'a,'b>(data: &'a str, key: &'b str) -> &'a str {
  |     ^

error: aborting due to previous error

thread 'Lifetimes_2' panicked at 'Box<Any>', src/librustc_errors/lib.rs:694
thread 'Lifetimes_2' panicked at 'couldn't compile the test', src/librustdoc/test.rs:283


failures:
    Lifetimes_0
    Lifetimes_1
    Lifetimes_2

test result: FAILED. 10 passed; 3 failed; 10 ignored; 0 measured

You'll need to put "```rust,no_run" at the top of your examples when you know they won't compile. Or possibly there is a way nowadays to annotate them with the expected error, cc @GuillaumeGomez ?

@Rantanen
Copy link
Contributor Author

Took care of those issues hopefully.

  • Removed the = &0 assignment. Was debating, whether I should still add : &u32 for clarity but omitted it for now. I'm still unnerved by how good Rust's type inference is when the variable isn't used immediately. :)
  • <'a,'b> -> <'a, 'b>. One instance in code, one in text.
  • v : &str -> v: &str.
  • lifetime -> lifetimes.
  • Added ignore in the code examples. This was used in the other code examples in the same file. What's the difference between ignore and no_run?

Unfortunately I can't test the last one locally as the local Rust installation is interfering with the make check I'm guessing. Some of the llvm-phase tests are failing to found crate 'std' compiled by an incompatible version of rustc when running make check.

@GuillaumeGomez
Copy link
Member

@durka: There is. If you expect an error, you have to add "compile_fail" and the expected error code (this second part isn't mandatory but it's better to have it). So for example:

    ```compile_fail,E0532
// you code...
    ```

Copy link
Member

@steveklabnik steveklabnik left a comment

Choose a reason for hiding this comment

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

Thank you for this! This is good overall, but some small nits.

@@ -50,29 +50,87 @@ complicated. For example, imagine this set of operations:
4. You decide to use the resource.

Uh oh! Your reference is pointing to an invalid resource. This is called a
dangling pointer or ‘use after free’, when the resource is memory.
dangling pointer or ‘use after free’, when the resource is memory. In a
simple case this would look like:
Copy link
Member

Choose a reason for hiding this comment

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

Teaching advice: I would stay away from "simple". Say something like "In a small case like this" or just "in a case like this."

The reason is, it might be simple to you, but not to your reader. If they hear you say "this is simple" and then they look at it and go "I have no idea what's going on", they get the impression that the topic isn't for them, and bail.

This applies almost universally in teaching, but especially in the chapter on the most complex topic in Rust 😄


To fix this, we have to make sure that step four never happens after step
three. The ownership system in Rust does this through a concept called
lifetimes, which describe the scope that a reference is valid for.
three. In the simple example above the Rust compiler is able to report the issue
Copy link
Member

Choose a reason for hiding this comment

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

same here

let data = "#1=foo;#2=bar";
let v: &str;
{
let key = format!("#{}", id); // -o key goes into scope
Copy link
Member

Choose a reason for hiding this comment

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

we use + rather than o elsehwere in this chapter for this

["Lifetime Elision"][lifetime-elision] below) them in common cases.
Before we get to that, though, let’s break the explicit example down:
["Lifetime Elision"][lifetime-elision] below) them in common cases. Before we
get to that, though, let’s look at a simple example with explicit lifetimes:
Copy link
Member

Choose a reason for hiding this comment

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

same here with simple

@@ -359,3 +418,4 @@ fn args<'a, 'b, T: ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command; // e
fn new(buf: &mut [u8]) -> BufWriter; // elided
fn new<'a>(buf: &'a mut [u8]) -> BufWriter<'a>; // expanded
```

Copy link
Member

Choose a reason for hiding this comment

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

✂️

@Rantanen
Copy link
Contributor Author

Changed to use compile_fail where applicable.

  • The i does not live long enough error didn't list the error code so the first example is missing that one.
  • Used # to include code in the compilation (but not HTML output) in one of the examples. This made ignore/compile_fail obsolete as the example compiles with the new code.

Changes based on the review comments.

  • Implemented more or less as suggested.

Changed the second example.

The original felt contrived to me and I was never that happy with it. It tried to have a purpose while demonstrating the problem. The new one still has a purpose, but (at least to me) it feels more obvious, which makes it less distracting.

Of course someone could argue that the example should be completely abstract with fn f(a: &str, b: &str) and variables a and b with no format! shenanigans. I'm fine changing it into this as well.

Minor:

  • Renamed the foo functions to main to make the examples work as stand alone programs.

(Also found make check-docs for testing the docs locally despite the normal make check failing \o/)

@Rantanen
Copy link
Contributor Author

There seems to be some inconsistencies concerning the main() functions vs "global" scope in the other examples in the book.

Which one is the preferred style for the examples?

Without explicit main():

fn take(v: Vec<i32>) {
    // what happens here isn’t important.
}

let v = vec![1, 2, 3];

take(v);

println!("v[0] is: {}", v[0]);

With explicit main()

fn main() {
    let a = 5;

    let _y = double(a);
    println!("{}", a);
}

fn double(x: i32) -> i32 {
    x * 2
}

Personally (now that I realize you can do it), I rather like the one without the main() for having better example-to-noise ratio.

@steveklabnik
Copy link
Member

In general, without explicit main is considered best. Sometimes, you have to have it though. But 99% of the time, you don't.

@bors
Copy link
Contributor

bors commented Oct 12, 2016

☔ The latest upstream changes (presumably #37090) made this pull request unmergeable. Please resolve the merge conflicts.

@Rantanen
Copy link
Contributor Author

Didn't see guidelines for resolving the merge conflicts. I went with merging master to the PR. Is this okay, or would a rebase on top of the new master have been preferred?

Also curious on whether I should keep maintaining the PR. I'm guessing most of the documentation resources are going into rust-lang/book currently. If I should keep maintaining it, is there any status information?

(Mainly I'm curious on whether this PR is in some kind of a limbo due to open change requests. Is there a way to mark those as "done". Couldn't find any documentation in the GitHub review article.)

@steveklabnik
Copy link
Member

A rebase on top of the new master is better, actually.

Also curious on whether I should keep maintaining the PR. I'm guessing most of the documentation resources are going into rust-lang/book currently. If I should keep maintaining it, is there any status information?

Yes please! That book is still months out from launching, this helps everyone in the meantime. And since my focus is on that, it's extra valuable to have people help out here.

(Mainly I'm curious on whether this PR is in some kind of a limbo due to open change requests. Is there a way to mark those as "done". Couldn't find any documentation in the GitHub review article.)

There's two reasons:

  1. I missed the "a new commit was pushed" email to this PR, which is my fault.
  2. This PR is assigned to @Manishearth , and should have been assigned to me. So I didn't see it in my "hey what are the PRs I'm assigned to" page. It's my fault for not double checking and assigning to me when I initially gave you feedback, sorry about that!

So first, let's fix that:

@bors: r? @steveklabnik

Second, I let me review this again, and then you can do the rebase and/or take care of any other nits, and then we can merge this. So sorry again.

@steveklabnik
Copy link
Member

Yes, this looks great. If you can do that rebase, I can get this merged. Sorry again.

Start the lifetime section with an explanation of the issues that
lack of explicit lifetimes cause and how lifetimes alleviate these.
@Rantanen
Copy link
Contributor Author

No problem! Was partly my fault to begin with as I didn't request you to review the code like suggested in the contribution guidelines.

Rebase done.

@GuillaumeGomez
Copy link
Member

Thanks!

@bors: r+ rollup

@bors
Copy link
Contributor

bors commented Oct 13, 2016

📌 Commit cb90723 has been approved by GuillaumeGomez

@bors
Copy link
Contributor

bors commented Oct 13, 2016

⌛ Testing commit cb90723 with merge 9cb498d...

@bors
Copy link
Contributor

bors commented Oct 13, 2016

💔 Test failed - auto-linux-cross-opt

@Rantanen
Copy link
Contributor Author

Seems like the auto-linux-cross-opt build was broken last night (UTC anyway) so I'm assuming there's nothing for me to do to get that resolved.

checking whether the C compiler works... no
configure: error: in `/buildslave/rust-buildbot/slave/auto-linux-cross-opt/build/obj/powerpc64-unknown-linux-gnu/rt/jemalloc':
configure: error: C compiler cannot create executables
See `config.log' for more details
make: *** [powerpc64-unknown-linux-gnu/rt/jemalloc/lib/libjemalloc_pic.a] Error 77
make: *** Waiting for unfinished jobs....

@durka
Copy link
Contributor

durka commented Oct 13, 2016

That's the same bogus failure from #37119.

On Thu, Oct 13, 2016 at 11:47 AM, Mikko Rantanen notifications@github.com
wrote:

Seems like the auto-linux-cross-opt
https://buildbot.rust-lang.org/builders/auto-linux-cross-opt?numbuilds=25
build was broken last night (UTC anyway) so I'm assuming there's nothing
for me to do to get that resolved.

checking whether the C compiler works... no
configure: error: in /buildslave/rust-buildbot/slave/auto-linux-cross-opt/build/obj/powerpc64-unknown-linux-gnu/rt/jemalloc': configure: error: C compiler cannot create executables Seeconfig.log' for more details
make: *** [powerpc64-unknown-linux-gnu/rt/jemalloc/lib/libjemalloc_pic.a] Error 77
make: *** Waiting for unfinished jobs....


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#36755 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAC3n8f82SQAadrE-O0CWr_O5B3CIGP1ks5qzlKigaJpZM4KG7Cl
.

@arielb1
Copy link
Contributor

arielb1 commented Oct 13, 2016

@bors retry

@alexcrichton
Copy link
Member

@bors: retry

sophiajt pushed a commit to sophiajt/rust that referenced this pull request Oct 14, 2016
Explain motivation behind lifetimes

Start the lifetime section with an explanation of the issues that lack of explicit lifetimes cause and how the explicit lifetimes solve these.

----------------

I had really hard time figuring out why I would need to care about the explicit reference lifetimes when going through the book at first. With strong background in C++, I'm familiar with the dangling reference problem - but given the section seems to focus more on the lifetime syntax and various ways to define lifetimes on functions and structs, I was unable to understand how they are used to solve the reference problem.

This PR is an attempt at getting the reader to understand what the explicit lifetimes are used for and why they are an awesome thing instead of a bit of syntax that just has to be written.

It's been less than a week that I've been diving into Rust so I'm far from certain about the terminology and technical correctness. I tried mimicking the existing terminology from the lifetimes section, but still no promises on getting it right.
bors added a commit that referenced this pull request Oct 14, 2016
Rollup of 10 pull requests

- Successful merges: #36307, #36755, #36961, #37102, #37115, #37119, #37122, #37123, #37141, #37159
- Failed merges:
@bors bors merged commit cb90723 into rust-lang:master Oct 15, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants