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

chapter 10: generics #249

Merged
merged 38 commits into from Nov 14, 2016

Conversation

@steveklabnik
Copy link
Member

steveklabnik commented Sep 6, 2016

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:

  • Add filenames to code examples where it makes sense
  • Switch underscores used for emphasis to asterisks for consistency

@steveklabnik steveklabnik force-pushed the generics branch from 2cd36e3 to f094d8b Sep 6, 2016

@@ -0,0 +1,231 @@
# Traits

At the end of the last section, we had this code:

This comment has been minimized.

@carols10cents

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.

@steveklabnik

steveklabnik Sep 7, 2016

Author Member

I lean towards 2, but could go for 1, I guess.

This comment has been minimized.

@steveklabnik

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.

@carols10cents

carols10cents Sep 8, 2016

Member

THINK OF THE ENVIRONMENT @jonathandturner 🌳 🌳 🌳 🌳 📄 📄 📄 📄 📄

This comment has been minimized.

@jonathandturner

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.

@carols10cents

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 :)

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.

@jonathandturner

jonathandturner Sep 8, 2016

Collaborator

nit: maybe "Option" rather than "option"

printing. We could write it like this:

```rust,ignore
fn print(value: v) {

This comment has been minimized.

@jonathandturner

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.

@steveklabnik

steveklabnik Sep 14, 2016

Author Member

Whoops! Totally.

@jonathandturner

This comment has been minimized.

Copy link
Collaborator

jonathandturner commented Sep 12, 2016

Definitely 👍 on the direction. I think it reads well and makes generics feel more like the mechanical translation they are. I made a few comments, but I didn't see anything major. I'd say it's definitely good enough to fill out with this approach.

```

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.

@sgrif

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?

@carols10cents

This comment has been minimized.

Copy link
Member

carols10cents commented Sep 28, 2016

things steve is planning on covering in the lifetimes section in this chapter:

  • syntax (example with reference stored in a struct, impl, 'static)
  • lifetimes are generic over scopes
  • rust uses these to prevent your refs from dangling
  • elision
  • tie lifetimes of two things together
@steveklabnik

This comment has been minimized.

Copy link
Member Author

steveklabnik commented Oct 4, 2016

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 steveklabnik force-pushed the generics branch from 6103656 to c01f6bf Oct 4, 2016

@steveklabnik

This comment has been minimized.

Copy link
Member Author

steveklabnik commented Oct 4, 2016

(also i rebased)

@carols10cents
Copy link
Member

carols10cents left a comment

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.

@carols10cents

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.

@steveklabnik

steveklabnik Oct 5, 2016

Author Member

Yes, I'm into it!

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.

@carols10cents

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.

@steveklabnik

steveklabnik Oct 5, 2016

Author Member

yeah, the ordering is a bit odd. I like that new wording.

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.

@carols10cents

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.

@steveklabnik

steveklabnik Oct 5, 2016

Author Member

👍

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.

@carols10cents

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?

@carols10cents

This comment has been minimized.

Copy link
Member

carols10cents commented Oct 5, 2016

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 &str, and the error messages guide them to lifetimes, rather than to "you probably shouldn't do that".

I think we have an opportunity here to explain that you probably shouldn't do that and why :)

For example:

fn let_me_return_a_ref() -> &str {
    "foo"
}

fn main() {
    let foo = let_me_return_a_ref();
}

results in this error:

error[E0106]: missing lifetime specifier
 --> src/main.rs:1:29
  |
1 | fn let_me_return_a_ref() -> &str {
  |                             ^ expected lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
  = help: consider giving it a 'static lifetime

That error in particular might be a way to segue to 'static?

Some more examples:

@steveklabnik

This comment has been minimized.

Copy link
Member Author

steveklabnik commented Oct 6, 2016

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?

```
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.

@carols10cents

carols10cents Oct 10, 2016

Member

And why didn't adding this parameter fix the error?

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.

@carols10cents

carols10cents Oct 10, 2016

Member

what if we just... introduce them right here?

This comment has been minimized.

@steveklabnik

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.

@carols10cents

carols10cents Oct 10, 2016

Member

Back to chapter 3 then?

This comment has been minimized.

@steveklabnik

steveklabnik Oct 10, 2016

Author Member

yeah i think so, probably. ugh

This comment has been minimized.

@carols10cents

carols10cents Nov 7, 2016

Member

ugh poo i just sent chapter 3 back to them without remembering this.

@carols10cents

This comment has been minimized.

Copy link
Member

carols10cents commented Oct 10, 2016

I like the direction, I could work with this! Looks like there's a few code examples that need ignore tho. And we have to figure out what to do with const/static gah

@steveklabnik

This comment has been minimized.

Copy link
Member Author

steveklabnik commented Oct 10, 2016

Looks like there's a few code examples that need ignore tho.

Ah yes 😓

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.

@leodasvacas

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.

@carols10cents

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!

trait Printable {
fn print(&self);
fn print_debug(&self);

This comment has been minimized.

@kornelski

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.

# fn print(&self);
# }
#
struct Point {

This comment has been minimized.

@kornelski

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.

@carols10cents

carols10cents Nov 12, 2016

Member

Good point. Although making Printable work for Point<T> might be a fun exercise!


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.

@kornelski

kornelski Nov 12, 2016

Contributor

I feel like the book mixes "you" and "we" voice unnecessarily.

This comment has been minimized.

@carols10cents

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.


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.

@kornelski

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.

@carols10cents

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.

@kornelski

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.

@steveklabnik

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.

@kornelski

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.

@steveklabnik

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.

@kornelski

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.

"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.

@kornelski

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.

@carols10cents

carols10cents Nov 12, 2016

Member

I totally hear you here.

fn foo<'a>() -> &'a i32 {
let x = 5;
&x

This comment has been minimized.

@kornelski

kornelski Nov 12, 2016

Contributor

That doesn't compile, so I'm lost.

x: i32,
y: i32,
enum Temperature {
Celsius(i32),

This comment has been minimized.

@kornelski

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.

@steveklabnik

steveklabnik Nov 12, 2016

Author Member

Printable would be Display/debug

This comment has been minimized.

@carols10cents

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.

@kornelski

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.

@carols10cents

This comment has been minimized.

Copy link
Member

carols10cents commented Nov 13, 2016

In thinking about this comment where I said:

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 &str, and the error messages guide them to lifetimes, rather than to "you probably shouldn't do that".

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

This comment has been minimized.

Copy link
Member

carols10cents commented Nov 13, 2016

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 foo, but then it introduces everything kind of all at once instead of in gradual steps like you had it... i'm not sure how i feel about it yet.

@gnzlbg

This comment has been minimized.

Copy link

gnzlbg commented Nov 14, 2016

The "Printable Temperature" example achieves its goal really well, but I wonder whether it could be reused to show impl Trait and specialization. Maybe it would be worth it to explore whether it can be used to explain these features as well (and if not use a different example).

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 impl Trait it might be obvious, but in particular for specialization we want to have a solid example that makes it easy to teach, and that is something the book might want to "build up" throughout the generics/traits section.

@steveklabnik
Copy link
Member Author

steveklabnik left a comment

I'm happy with this. Let's :shipit:

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.

@steveklabnik

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.

@carols10cents

carols10cents Nov 14, 2016

Member

oh boy that's unfortunate

@@ -0,0 +1,272 @@
## Traits

Rust has a feature called *traits*. Traits are similar to a feature often

This comment has been minimized.

@steveklabnik

steveklabnik Nov 14, 2016

Author Member

this first sentence is fine but also a bit boring, idk

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.

@steveklabnik

steveklabnik Nov 14, 2016

Author Member

should we use 20 even though technically it's not final yet?

This comment has been minimized.

@carols10cents

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.

@steveklabnik

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.

@carols10cents
@steveklabnik

This comment has been minimized.

Copy link
Member Author

steveklabnik commented Nov 14, 2016

@gnzlbg

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.

@gnzlbg

This comment has been minimized.

Copy link

gnzlbg commented Nov 14, 2016

I'd prefer to come back to this chapter and work on that if and when those features actually ship.

Thinking more about it, things like the lattice rule for specialization or impl Trait in function arguments could significantly change "the kind of example needed". So without knowing about those it might be a waste of time to think about this right now :/

@carols10cents carols10cents merged commit c346df7 into master Nov 14, 2016

0 of 2 checks passed

continuous-integration/travis-ci/pr The Travis CI build is in progress
Details
continuous-integration/travis-ci/push The Travis CI build is in progress
Details

@carols10cents carols10cents deleted the generics branch Nov 14, 2016

@carols10cents

This comment has been minimized.

Copy link
Member

carols10cents commented Nov 14, 2016

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!


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.

@Luke-Nukem

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.