Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upchapter 10: generics #249
Conversation
steveklabnik
force-pushed the
generics
branch
from
2cd36e3
to
f094d8b
Sep 6, 2016
carols10cents
reviewed
Sep 7, 2016
| @@ -0,0 +1,231 @@ | |||
| # Traits | |||
|
|
|||
| At the end of the last section, we had this code: | |||
This comment has been minimized.
This comment has been minimized.
carols10cents
Sep 7, 2016
Member
Liz has been discouraging such quick repetition of code like this-- it makes sense on the web since you've changed pages, but in print this page and the previous one could potentially be facing each other, so I see what she means. Idk if you want to:
- Remove the repetition from the previous chapter and just hint at the problem instead of showing code (i like this option most)
- Have this be in the web version but not print, I'm happy to cut things out when I ship chapters to nostarch but it'd be nice to have comments saying something like "start print cut here" and "end print cut here" and have the remaining text make sense without further editing needed (i like this option a middling amount)
- Remove the repetition here (i like this option least)
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
steveklabnik
Sep 7, 2016
Author
Member
I am trying to solve @jonathandturner 's "just dive in at any point" issues with stuff like this
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
jonathandturner
Sep 8, 2016
Collaborator
@carols10cents - I am. The environment of the mind.... wwwwoooooaaahhhhhhh ;)
Yeah, I think it's fine to have one repeated on the web but not in print, if that's doable
This comment has been minimized.
This comment has been minimized.
carols10cents
Nov 11, 2016
Member
I'm picking this PR up, and I think what I'd prefer to do is get this chapter ready for print first, then go back and figure out the web stuff at a later time. If anyone would like to help me with this, it would be much appreciated :)
jonathandturner
reviewed
Sep 8, 2016
| words, the idea of "an optional value" is a higher-order concept than any | ||
| specific type. We want it to work for any type at all. | ||
|
|
||
| We can do that with generics. In fact, that's how the actual option type works |
This comment has been minimized.
This comment has been minimized.
jonathandturner
reviewed
Sep 9, 2016
| printing. We could write it like this: | ||
|
|
||
| ```rust,ignore | ||
| fn print(value: v) { |
This comment has been minimized.
This comment has been minimized.
jonathandturner
Sep 9, 2016
Collaborator
I think this should be:
fn print<T>(value: T) {
value.print();
}Since I don't think there's a v in scope as a type? It's later invoked as a value (though never brought in as a value).
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Definitely |
carols10cents
assigned
steveklabnik
Sep 15, 2016
sgrif
reviewed
Sep 17, 2016
| ``` | ||
|
|
||
| We've left in the `Option::` bit for consistency with the previous examples, but | ||
| since `Option<T>` is in the prelude, it's not needed: |
This comment has been minimized.
This comment has been minimized.
sgrif
Sep 17, 2016
Do you think that this gives too much of an impression that enums in prelude are magic? Do you think it makes sense to say "since use Option::* is in the prelude" instead?
This comment has been minimized.
This comment has been minimized.
|
things steve is planning on covering in the lifetimes section in this chapter:
|
This comment has been minimized.
This comment has been minimized.
|
Okay, I pushed some bones for the lifetimes section. It's not my best work, but it's forward motion at least. what do you think? |
steveklabnik
force-pushed the
generics
branch
from
6103656
to
c01f6bf
Oct 4, 2016
This comment has been minimized.
This comment has been minimized.
|
(also i rebased) |
carols10cents
requested changes
Oct 5, 2016
|
I think this is moving in the right direction! Just a few questions and feelings, but I think I could work with everything here, if you add the static lifetime section that it looks like you're planning on doing. |
| // | | ||
| println!("r: {}", r); // | | ||
| // | | ||
| // -------+ |
This comment has been minimized.
This comment has been minimized.
carols10cents
Oct 5, 2016
Member
I think these examples would be clearer if there were outer {}s around all the code, so that r definitely goes out of scope. Bonus is that if the { is the first line, 'a doesn't start there.
I'm happy to make this change but wanted to check that this would be ok, wdyt?
This comment has been minimized.
This comment has been minimized.
| refers to, `x`. | ||
| Note that we didn't have to name any lifetimes here; Rust figured it out for | ||
| us. We only name lifetimes when we accept a reference as an argument, either |
This comment has been minimized.
This comment has been minimized.
carols10cents
Oct 5, 2016
Member
"We only name lifetimes when we accept a reference as an argument" is a little confusing imo because I know about the elision rules. Plus we have shown &self at this point, but we didn't use lifetimes there. Basically this sentence is implying to me "Rust can't figure out lifetimes when we accept a reference as any argument, so we must name lifetimes whenever we accept a reference as an argument" but that's not true.
Maybe change to "The only situation in which Rust can't figure out the lifetimes is when we have a function or method that accepts a reference as an argument, except for a few scenarios we'll discuss in the lifetime elision section"?
This comment has been minimized.
This comment has been minimized.
| and `'b` were concrete lifetimes: we knew about `x` and `r`, and how long they | ||
| would live exactly. But when we write a function, we can't know exactly all of | ||
| the arguments that it would be called with; so we have to explain to Rust what | ||
| we'd expect the lifetime of the argument to be. This is similar to how when we |
This comment has been minimized.
This comment has been minimized.
carols10cents
Oct 5, 2016
Member
Recalling my beginners mind, I think if I read this while I was trying to understand lifetimes I would be yelling "I don't know what I expect the lifetime of the argument to be, how am I supposed to explain it to Rust?!!??!" at the screen. The answer to that might be "keep reading", but I wanted to record my feels at this point.
This comment has been minimized.
This comment has been minimized.
| So what does a lifetime parameter do, anyway? Consider this example: | ||
| ```rust,ignore | ||
| fn foo(x: &i32, y: &i32) -> &i32 { |
This comment has been minimized.
This comment has been minimized.
carols10cents
Oct 5, 2016
Member
Mmmm I feel we missed a step. Could we start with an example that uses one lifetime, has annotations similar to what you did for the lifetimes of x and r, and explains what, exactly, we're telling Rust?
This comment has been minimized.
This comment has been minimized.
|
Oh here's one more thing I think we should include at this point: there are many times I have seen people try to make a function that returns a reference to something that goes out of scope at the end of the function, often a I think we have an opportunity here to explain that you probably shouldn't do that and why :) For example:
results in this error:
That error in particular might be a way to segue to Some more examples: |
This comment has been minimized.
This comment has been minimized.
|
I pushed some more stuff. I started static, but then i realized, we haven't talked about const and static :( I tried to address the previous two criticisms and bulk up the single-reference case. Whatcha think? |
carols10cents
reviewed
Oct 10, 2016
| ``` | ||
| Now we have our "does not live long enough" error. What gives? Why does Rust | ||
| need this parameter in the first place? |
This comment has been minimized.
This comment has been minimized.
carols10cents
reviewed
Oct 10, 2016
| we could possibly return a reference from a function with no parameters is | ||
| if it were alive before the function executed. Hence, `'static`. | ||
| ZOMG WE HAVENT TALKED ABOUT CONST AND STATIC YET FUUUUUU |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
steveklabnik
Oct 10, 2016
Author
Member
feels a bit bad to introduce something totally unrelated to generics in the generics chapter. idk
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
carols10cents
Nov 7, 2016
Member
ugh poo i just sent chapter 3 back to them without remembering this.
This comment has been minimized.
This comment has been minimized.
|
I like the direction, I could work with this! Looks like there's a few code examples that need |
This comment has been minimized.
This comment has been minimized.
Ah yes |
leodasvacas
reviewed
Oct 23, 2016
| or `&mut self`, then the lifetime of `self` is the lifetime assigned to all | ||
| output lifetime parameters. | ||
| If none of these things are true, then you must explicitly annotate lifetimes. |
This comment has been minimized.
This comment has been minimized.
leodasvacas
Oct 23, 2016
Contributor
Explicitly annotate output lifetimes. Actually I think the way the elision rules are described in the RFC is clearer and more concise, maybe just copy that?
This comment has been minimized.
This comment has been minimized.
carols10cents
Nov 11, 2016
Member
I think this statement is meant to apply to all three rules-- the one about input lifetimes, and the two about output lifetimes. This sentence could definitely be clearer. Right now I'm thinking "If none of these three rules apply, then you must explicitly annotate input and output lifetimes", and I'm going to make that change, but I'm currently looking at this locally and I might come back when I'm doing a more comprehensive pass through and change this entirely. I will keep the way it's stated in the RFC in mind, there are parts of that I like better. Thank you for the pointer!
steveklabnik
and others
added some commits
Aug 31, 2016
kornelski
reviewed
Nov 12, 2016
| trait Printable { | ||
| fn print(&self); | ||
| fn print_debug(&self); |
This comment has been minimized.
This comment has been minimized.
kornelski
Nov 12, 2016
Contributor
Adding and removing that seems to break flow of the example. Maybe it'd be enough to say in the prose that multiple methods are allowed.
kornelski
reviewed
Nov 12, 2016
| # fn print(&self); | ||
| # } | ||
| # | ||
| struct Point { |
This comment has been minimized.
This comment has been minimized.
kornelski
Nov 12, 2016
•
Contributor
You've used generic Point before, so seeing non-generic Point here feels like a regression to me, and makes me wonder how to make Printable work for Point<T>.
Maybe use different, new types for this example? e.g. Celcius(i32) and Farenheit(i32)?
This comment has been minimized.
This comment has been minimized.
carols10cents
Nov 12, 2016
Member
Good point. Although making Printable work for Point<T> might be a fun exercise!
kornelski
reviewed
Nov 12, 2016
|
|
||
| Note that in order to use a trait's methods, the trait itself must be in scope. | ||
| If the definition of `Printable` was in a module, the definition would need to | ||
| be defined as `pub` and we would need to `use` the trait in the scope where we |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
carols10cents
Nov 12, 2016
Member
We're definitely trying to figure out our conventions. We very much want the book to read like the authors and the reader are working through problems together, but we switch to "you" when talking about things the reader needs to do or things the reader would do on their own in the future. That's the way I've been thinking about it anyway. This is definitely something that the No Starch editors are helping us with.
kornelski
reviewed
Nov 12, 2016
|
|
||
| Scopes of code? Yes, it's a bit unusual. Lifetimes are, in some ways, Rust's | ||
| most distinctive feature. They are a bit different than the tools you have used | ||
| in other programming languages. Lifetimes are a big topic, so we're not going |
This comment has been minimized.
This comment has been minimized.
kornelski
Nov 12, 2016
Contributor
I think lifetimes deserve a better introduction than via sidenote in another chapter.
Instead of dragging lifetimes into generics chapter, it may be better to mention lifetime generics after the lifetimes chapter.
This comment has been minimized.
This comment has been minimized.
carols10cents
Nov 12, 2016
Member
Instead of dragging lifetimes into generics chapter, it may be better to mention lifetime generics after the lifetimes chapter.
Do you mean "after the lifetimes chapter" or "after the generics chapter"?
We've talked to a lot of people who didn't really understand lifetimes until they realized that lifetimes were another kind of generic, so we're trying to lead with that explicitly to help people understand them more quickly. I don't think we as a community have found the absolute best way of teaching lifetimes yet (or even the best way to understand and think about them, for that matter).
Also note that we're planning a chapter on advanced lifetime stuff later in the book, but that I think we decided to do that after Steve had started on this section, so this chapter might need to be cut down a bit more.
This comment has been minimized.
This comment has been minimized.
kornelski
Nov 12, 2016
•
Contributor
I mean after lifetimes chapter, i.e. introduce generics over types, introduce lifetimes (in any order), and once the reader understands both concepts, explain generics over lifetimes.
My reasoning is that it's hard to adequately explain lifetimes, and it's a lot to explain just to explain what <'a> does. OTOH once the reader understands type generics and lifetimes on their own, introducing generics over lifetimes is much simpler and boils down to saying that these two work together too.
To me the syntax of <'a, T> was confusing. It's non-self-explanatory and a very Rust-specific thing. However, knowing that Rust requires these annotations doesn't really advance my understanding of lifetimes themselves.
My background is C, so my mental model of lifetimes is strictly around malloc/free/stack.
AFAIK generics over types affect code generation, but generics over lifetimes are a purely abstract compile-time type-checking feature, so I think of them as distinct features with an overlapping syntax.
This comment has been minimized.
This comment has been minimized.
steveklabnik
Nov 12, 2016
Author
Member
introduce lifetimes (in any order), and once the reader understands both concepts, explain generics over lifetimes.
Lifetimes fundamentally are generics, there's no lifetimes without generics. <'a> is a generic.
This comment has been minimized.
This comment has been minimized.
kornelski
Nov 12, 2016
•
Contributor
Lifetimes fundamentally are generics
I don't understand that.
{
let x = 1;
let y = &x;
}AFAIK &x has a lifetime here, and it's not generic.
In my mental model lifetimes are thing on their own without dependence on generics. Each reference (in source code) has a unique lifetime assigned at the point its created. It's only because each lifetime in Rust is treated as a separate, incompatible "type" that doesn't have a global name, it's necessary to use generics to be able to declare "any lifetime that outlives this" in function declarations, etc. Similarly with closures — they're concrete types, but it's just that Rust's syntax can't express that type (and it wouldn't be useful to do so), so generics are needed in practice.
This comment has been minimized.
This comment has been minimized.
steveklabnik
Nov 12, 2016
Author
Member
AFAIK &x has a lifetime here, and it's not generic.
Any time you write the lifetime syntax, which is the primary focus of the chapter, you're writing a generic parameter. Rust has no syntax for specifying specific, individual lifetimes.
(which is what you go on to say)
This comment has been minimized.
This comment has been minimized.
kornelski
Nov 12, 2016
Contributor
Any time you write the lifetime syntax
I see you're calling the 'a syntax a lifetime, but I think of "lifetime" to mean "time between something is allocated and freed". In my understanding whenever rustc says "does not live long enough" that is due to a lifetime mismatch, even if 'a is never written anywhere in the program.
kornelski
reviewed
Nov 12, 2016
| "a `'static` lifetime," but let's ignore that for now. We'll come back to it at | ||
| the end of the chapter. | ||
|
|
||
| Let's add a parameter. It won't quite fix it, but we get a different error: |
This comment has been minimized.
This comment has been minimized.
kornelski
Nov 12, 2016
Contributor
I've lost the plot here. Non-working example after non-working example, and I don't see where it's heading.
This comment has been minimized.
This comment has been minimized.
kornelski
reviewed
Nov 12, 2016
| fn foo<'a>() -> &'a i32 { | ||
| let x = 5; | ||
| &x |
This comment has been minimized.
This comment has been minimized.
carols10cents
added some commits
Nov 12, 2016
kornelski
reviewed
Nov 12, 2016
| x: i32, | ||
| y: i32, | ||
| enum Temperature { | ||
| Celsius(i32), |
This comment has been minimized.
This comment has been minimized.
kornelski
Nov 12, 2016
Contributor
Hmm, that's better idiomatic Rust than what I had in mind, but I thought 2 structs would show usage of a trait better:
struct Celcius(i32);
struct Fahrenheit(i32);
impl Printable for Celsius {…}
impl Printable for Fahrenheit {…}
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
carols10cents
Nov 13, 2016
Member
Printable would be Display/debug
@steveklabnik I'm not sure what you mean by this... this example is in the section where we're showing a Printable trait, before we say that Printable is like Display.
@pornel why would two structs be better?
This comment has been minimized.
This comment has been minimized.
kornelski
Nov 13, 2016
Contributor
The purpose of generics/trait bounds is to make one function work with more than one type. When you show it working more than one type it's clear how that's different than hardcoding one type. Without multiple types involved the example could have been written easily without generics.
This comment has been minimized.
This comment has been minimized.
|
In thinking about this comment where I said:
Plus this bug about guiding people to use owned types, I think the "returning a reference from a function" part should be in chapter 4, in the references section, and what we were talking about in #305 should indeed go in structs (chapter 5), and then in the lifetimes section here we could refer back to those two things and explain how to actually do them with lifetimes here... maybe? |
carols10cents
added some commits
Nov 13, 2016
This comment has been minimized.
This comment has been minimized.
|
I think I've convinced myself back the other way-- I don't think we need to show returning a reference in ch 4, i think i worked in into the lifetime section ok here. The only issue would be the time the reader is between ch 4 and ch 10 where they might try returning a reference.... @steveklabnik i'd love for you to take a look at the lifetime syntax chapter-- I changed the example to be a little more concrete with something that the reader might actually try to do rather than |
carols10cents
assigned
steveklabnik
and unassigned
carols10cents
Nov 13, 2016
carols10cents
approved these changes
Nov 13, 2016
This comment has been minimized.
This comment has been minimized.
gnzlbg
commented
Nov 14, 2016
|
The "Printable Temperature" example achieves its goal really well, but I wonder whether it could be reused to show At some point these features are going to be ready, and the book should make them feel like a natural thing one might want to do with traits. For |
steveklabnik
reviewed
Nov 14, 2016
|
I'm happy with this. Let's |
| never dug into what exactly they are or how to use them. In places where we | ||
| specify a type, like function signatures or structs, instead we can use | ||
| *generics*. Generics are stand-ins that represent more than one particular | ||
| concept. In this section, we're going to cover generic *data types*. |
This comment has been minimized.
This comment has been minimized.
steveklabnik
Nov 14, 2016
Author
Member
extreme nit: C++'s version of traits is called "concepts". so we may not want to use "concept" here.
i might be a bit overboard with this one, idk
This comment has been minimized.
This comment has been minimized.
| @@ -0,0 +1,272 @@ | |||
| ## Traits | |||
|
|
|||
| Rust has a feature called *traits*. Traits are similar to a feature often | |||
This comment has been minimized.
This comment has been minimized.
| in other programming languages. Lifetimes are a big topic, so we're not going | ||
| to cover everything about them in this chapter. What we *are* going to do is | ||
| talk about the very basics of lifetimes, so that when you see the syntax in | ||
| documentation or other places, you'll be familiar with the concepts. Chapter 20 |
This comment has been minimized.
This comment has been minimized.
steveklabnik
Nov 14, 2016
Author
Member
should we use 20 even though technically it's not final yet?
This comment has been minimized.
This comment has been minimized.
carols10cents
Nov 14, 2016
Member
I'm going with the numbers we have, I'm expecting once the whole book shakes out we'll check all of these.
| reference, except for a few scenarios we'll discuss in the lifetime elision | ||
| section. | ||
| Another time that Rust can't figure out the lifetimes is when structs have a |
This comment has been minimized.
This comment has been minimized.
steveklabnik
Nov 14, 2016
Author
Member
we might want to give this a heading, so it's easy to link to
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
This is a good point! The issue is, since these features aren't done yet, I'm not sure we can even say. We could figure it out for the current implementation, but it might change between now and then. I'd prefer to come back to this chapter and work on that if and when those features actually ship. |
carols10cents
added some commits
Nov 14, 2016
This comment has been minimized.
This comment has been minimized.
gnzlbg
commented
Nov 14, 2016
Thinking more about it, things like the lattice rule for specialization or |
carols10cents
merged commit c346df7
into
master
Nov 14, 2016
carols10cents
deleted the
generics
branch
Nov 14, 2016
This comment has been minimized.
This comment has been minimized.
|
Thank you everyone for your feedback, we decided to merge this and send it off to our editors, but we'll have a few rounds of edits on this and will be able to incorporate any other feedback at those points. Please feel free to open issues or PRs with additional thoughts! |
Luke-Nukem
reviewed
Nov 14, 2016
|
|
||
| You can recognize when any kind of generics are used by the way that they fit | ||
| into Rust's syntax: any time you see angle brackets, `<>`, you're dealing with | ||
| generics. Types we've seen before, like in Chapter 8 where we discussed vectors |
This comment has been minimized.
This comment has been minimized.
Luke-Nukem
Nov 14, 2016
perhaps a small addition here along the lines of "you're dealing with a function/data that uses generics within it". Not entirely sure how to word it, but it feels as if being a bit more specific about it would help. This would likely help with the next sentence using vec<i32> as an example.
steveklabnik commentedSep 6, 2016
•
edited by carols10cents
Reviewers: Please read the added text for these chapters and leave any comments you have on this PR! Thank you for your help!
Carol TODO: