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

Book: Be very explicit of lifetimes being descriptive #36997

Merged
merged 5 commits into from
Oct 12, 2016
Merged

Book: Be very explicit of lifetimes being descriptive #36997

merged 5 commits into from
Oct 12, 2016

Conversation

KillTheMule
Copy link
Contributor

... not prescriptive. Pointed out in https://users.rust-lang.org/t/what-if-i-get-lifetimes-wrong/7535/4, which was a revelation to me and made me think this should be more clear in the book. I'm not sure if I got this entirely right or if the wording is good, but I figured a PR is more helpful than a simple issue.

r? @steveklabnik

Small Note: There's also https://github.com/rust-lang/book, should I have sent the PR there? It doesn't coincide with the online book though, so I figured it's better of here.

... no prescriptive. Pointed out in https://users.rust-lang.org/t/what-if-i-get-lifetimes-wrong/7535/4, which was a revelation to me and made me think this should be more clear in the book.
@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 @steveklabnik (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.

@@ -56,6 +56,8 @@ 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.

*Note* It's important to understand that lifetimes are _descriptive_ not _prescriptive_. This means that the lifetimes of references are determined by the code, not by the lifetime annotations. The annotations, however, point out the lifetimes to the compiler in case it can't figure them out by itself.
Copy link
Contributor

Choose a reason for hiding this comment

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

"the lifetimes of references are determined by the code, not by the lifetime annotations" is a bit ambiguous (because "lifetime" is super overloaded). Perhaps it would be better to spell out what the two occurrences of the word "lifetime" mean here:

  1. The time for which a reference is valid
  2. The 'a annotations

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 tried to make it clearer, making a distinction between "lifetime" and "lifetime annotation".

Also, emphasize differently.
@@ -56,7 +56,7 @@ 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.

*Note* It's important to understand that lifetimes are _descriptive_ not _prescriptive_. This means that the lifetimes of references are determined by the code, not by the lifetime annotations. The annotations, however, point out the lifetimes to the compiler in case it can't figure them out by itself.
**Note** It's important to understand that lifetime annotations are _descriptive_ not _prescriptive_. This means that the lifetimes of references are determined by the code, not by the annotations. The annotations, however, point out the lifetimes to the compiler in case it can't figure them out by itself.
Copy link
Contributor

Choose a reason for hiding this comment

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

That's better, but I also think the phrase "the lifetimes of references" should also avoid the overloaded term "lifetime" (just as it says "the annotations" a bit later). This paragraph is clarifying a confusion between the two meanings of the word lifetime (one I've see a lot) so it should avoid anything which could be misinterpreted through the lens of that confusion. Perhaps something along the lines of "how long references are valid is determined by [...]".

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 gave it another shot, thanks for looking at it.

@hanna-kruppe
Copy link
Contributor

Thanks a lot! I've seen a bunch of people who needed to see this distinction spelled out, and I'm almost ashamed that I never thought to try and put it into the prominent documentation rather than just explaining it again and again in person. I'm happy with the phrasing as-is, but I'm not really an authority on the book (not to mention lacking @bors privileges) so let's wait for what @steveklabnik says.

Oh and by the way:

Small Note: There's also https://github.com/rust-lang/book, should I have sent the PR there? It doesn't coincide with the online book though, so I figured it's better of here.

Here's the right place. That repository is the second edition of The Rust Programming Language, and it's in the process of being written from scratch. You can read the current state at http://rust-lang.github.io/book/ and, if you like, check if a similar notice would fit in there. But for now, this repository's version of the book is the one most people read.

@steveklabnik
Copy link
Member

Small Note: There's also https://github.com/rust-lang/book, should I have sent the PR there? It doesn't coincide with the online book though, so I figured it's better of here.

As @rkruppe says, it's being totally re-written. I'm actually working on the lifetimes section right now. So you couldn't have sent said PR, as it doesn't even exist in-tree yet 😄

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.

Thanks for this! Improving this stuff is really important.

@@ -56,6 +56,8 @@ 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.

**Note** It's important to understand that lifetime annotations are _descriptive_, not _prescriptive_. This means that how long a reference is valid is determined by the code, not by the annotations. The annotations, however, point out this fact to the compiler in case it can't figure it out by itself.
Copy link
Member

Choose a reason for hiding this comment

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

Could you wrap this to 80 columns please?

@@ -56,6 +56,8 @@ 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.

**Note** It's important to understand that lifetime annotations are _descriptive_, not _prescriptive_. This means that how long a reference is valid is determined by the code, not by the annotations. The annotations, however, point out this fact to the compiler in case it can't figure it out by itself.
Copy link
Member

Choose a reason for hiding this comment

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

The annotations, however, point out this fact to the compiler in case it can't figure it out by itself.

So, the compiler doesn't try to "figure out" the right lifetimes; it only applies the elision rules, which are just dumb patterns. So I would change this to something like,

The annotations, however, give the compiler the information it needs for more complex cases.

Or something like that. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, the compiler doesn't try to "figure out" the right lifetimes; it only applies the elision rules, which are just dumb patterns.

I don't really understand the inner workings of the compiler, but my thinking goes like this: Lifetime rules are for figuring out if it's ok do drop a certain value at the point where it goes out of scope (i.e. it's owner). For that, I assume the compiler does a "control lifetimes" pass, and if that's ok, it forgets about them and translates the code as is, without resorting to any lifetime anymore (this is what this is about, you can't influence the machine code by lifetimes, ony if it compiles or not). Elision let's the programmer leave out certain annotation, but that "just" means the compiler puts them in itself. After elision, it still has the "real work" of checking.

In that way, lifetime annotations "help the compiler figure out if the code is ok to compile".

Is that correct? I'll try to improve my wording, but I'll change it if my thoughts aren't correct.

Copy link
Member

Choose a reason for hiding this comment

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

Elision let's the programmer leave out certain annotation, but that "just" means the compiler puts them in itself. After elision, it still has the "real work" of checking.

Yes, that's correct.

@steveklabnik
Copy link
Member

@bors: r+ rollup

Thank you!

@bors
Copy link
Contributor

bors commented Oct 10, 2016

📌 Commit 0d0cdb7 has been approved by steveklabnik

GuillaumeGomez added a commit to GuillaumeGomez/rust that referenced this pull request Oct 11, 2016
Book: Be very explicit of lifetimes being descriptive

... not prescriptive. Pointed out in https://users.rust-lang.org/t/what-if-i-get-lifetimes-wrong/7535/4, which was a revelation to me and made me think this should be more clear in the book. I'm not sure if I got this entirely right or if the wording is good, but I figured a PR is more helpful than a simple issue.

r? @steveklabnik

Small Note: There's also https://github.com/rust-lang/book, should I have sent the PR there? It doesn't coincide with the online book though, so I figured it's better of here.
bors added a commit that referenced this pull request Oct 11, 2016
Rollup of 9 pull requests

- Successful merges: #36679, #36699, #36997, #37040, #37060, #37065, #37072, #37073, #37081
- Failed merges:
@bors bors merged commit 0d0cdb7 into rust-lang:master Oct 12, 2016
@leoyvens
Copy link
Contributor

I don't think this describe/prescribe distinction is helpful and I think this adds more confusion than solves, for two reasons:

  1. It's a lingustical subtelty, and therefore makes the explanation less concrete and more confusing. Does a program describe or prescribe it's runtime behaviour? What's the difference between an prescription that is realized and a description? The answers don't matter, the distinction is senseless in this context.
  2. I think it's actually the other way around. As a part of the function signature, lifetime annotations are a contract between caller and function body. So it is, as all contracts, prescriptive. Of course if you don't follow the prescribed contract the compiler will yell at you.

@steveklabnik
Copy link
Member

FWIW, I hear you here, though using this particular description has helped a number of people on #rust-beginners, who assume that you can use lifetimes to make sure something lives long enough.

@hanna-kruppe
Copy link
Contributor

hanna-kruppe commented Oct 19, 2016

It's true that the words "descriptive" and "prescriptive" can also be interpreted the other way around. However, those words aren't (and aren't supposed to) standing on their own — they're more of a slogan or mnemonic, only useful because it's associated with the full explanation which isn't ambiguous in the same way. This is also how it plays out on IRC: In addition to the slogan, you immediately get an elaboration similar to the contents of this PR.

@leoyvens
Copy link
Contributor

leoyvens commented Oct 19, 2016

However the explanation in this PR is also incorrect:

This means that how long a reference is valid is determined by the code, not by the annotations.

The entire purpose of annotations is to determine for how long a returned reference is valid. Consider:

fn main() { 
    let x = 1;
    let mut y = 2;
    let z = borrow(&x, &y);
    &mut y;
    z;
}
fn borrow<'a, 'b>(x : &'a i32, y : &'a i32) -> &'a i32 { &x }

Is the reference z still valid after &mut y;? Depends on the annotations! As the code stands it is not, but if you switch y : &'a i32 for y : &'b i32 then z is valid for the entire program.

@KillTheMule
Copy link
Contributor Author

KillTheMule commented Oct 19, 2016

Maybe I didn't phrase things correctly/clearly (I'm not a native english speaker), but this totally fits what I wanted to express.

The entire purpose of annotations is to determine for how long a returned reference is valid.

That's the point, I don't think this is true. The annotations don't determine how long a reference is valid, they just claim this to the compiler.

Let me slightly rewrite your example so I can reference this better:

fn main() { 
    let x = 1;
    let mut y = 2;
    let z = borrow(&x, &y);
    &mut y;
    z;
}
fn borrow<'a, 'b>(u : &'a i32, v : &'a i32) -> &'a i32 { &u }

Just renaming of function arguments, doesn't change anything.

So, when is v invalidated? It's easy to say, it's dropped after borrow ends, no matter what lifetimes you annotate. None of 'a or 'b change that, those only change what we claim to the compiler, and what we want him to check to be able to satisfy. But v goes out of scope at that point, and we can't change it.

It doesn't compile, because the above variant says (among other things) "Hey v (which is a reference to y) is valid as long as the reference that is returned by this function", which collides with &mut y;, so the borrow checker complains.

When you replace v: &'a i32 by v: &'b i32, this statement changes to an empty statement, which can't collide with anything, so the borrow checker doesn't complain. I'd claim that if you could somehow disable the borrow+lifetime checker, then both variants would produce the same machine code, even though we have different lifetime annotations. The scope is determined by the code, not the annotations.

I might have been a bit handwaving previously. When I say "Doesn't change the behavior of the code", I'm only referencing stuff that compiles. You can of course change annotations so that you don't get an output, and "no machine code" is different from "some machine code", alright.

To draw an analogy, compare using weight: i32 to struct kilos(i32); weight: kilos. Both will result in the same code (if it compiles), but in the second case the type checker will disallow you to do certain things (e.g. comparing to i32). Not because it couldn't produce code, but because the checks it does will fail.

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.

6 participants