From c8a327eca49d0e0ac698455a08020605cb8acc6c Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Tue, 11 Apr 2017 14:02:56 -0400 Subject: [PATCH] Edit associated types --- second-edition/src/ch13-02-iterators.md | 2 +- second-edition/src/ch19-03-advanced-traits.md | 206 +++++++++++++----- 2 files changed, 155 insertions(+), 53 deletions(-) diff --git a/second-edition/src/ch13-02-iterators.md b/second-edition/src/ch13-02-iterators.md index 1aa816ddc..a38844500 100644 --- a/second-edition/src/ch13-02-iterators.md +++ b/second-edition/src/ch13-02-iterators.md @@ -88,7 +88,7 @@ trait Iterator { There's some new syntax that we haven't covered here yet: `type Item` and `Self::Item` are defining an *associated type* with this trait, and we'll talk -about associated types in depth in Chapter XX. For now, all you need to know is +about associated types in depth in Chapter 19. For now, all you need to know is that this code says the `Iterator` trait requires that you also define an `Item` type, and this `Item` type is used in the return type of the `next` method. In other words, the `Item` type will be the type of element that's diff --git a/second-edition/src/ch19-03-advanced-traits.md b/second-edition/src/ch19-03-advanced-traits.md index 13dea789c..c819ae2fe 100644 --- a/second-edition/src/ch19-03-advanced-traits.md +++ b/second-edition/src/ch19-03-advanced-traits.md @@ -1,67 +1,89 @@ -# Advanced Traits +## Advanced Traits We covered traits in Chapter 10, but like lifetimes, we didn't get to all the details. Now that we know more Rust, we can get into the nitty-gritty. -## Associated Types +### Associated Types + +*Associated types* are a way of associating a type placeholder with a trait +such that the trait method definitions can use these placeholder types in their +signatures. The implementer of a trait will specify the concrete type to be +used in this type's place for the particular implementation. We've described most of the things in this chapter as being very rare. Associated types are somewhere in the middle; they're more rare than the rest of the book, but more common than many of the things in this chapter. -Associated types look like this: +An example of a trait with an associated type is the `Iterator` trait provided +by the standard library. It has an associated type named `Item` that stands in +for the type of the values that we're iterating over. We mentioned in Chapter +13 that the definition of the `Iterator` trait is as shown in Listing 19-20: ```rust -trait Foo { - type Bar; - - fn foo(&self) -> Self::Bar; -} - -impl Foo for i32 { - type Bar = String; - - fn foo(&self) -> Self::Bar { - self.to_string() - } +pub trait Iterator { + type Item; + fn next(&mut self) -> Option; } ``` -The trait `Foo` has an associated type called `Bar`. We can then use -`Self::Bar` elsewhere in our trait definition to use that type. +Listing 19-20: The definition of the `Iterator` trait +that has an associated type `Item` -This _feels_ like more generics. For example, this seems similar to -the following code: +This says that the `Iterator` trait has an associated type named `Item`. `Item` +is a placeholder type, and the return value of the `next` method will return +values of type `Option`. Implementers of this trait will specify +the concrete type for `Item`, and the `next` method will return an `Option` +containing a value of whatever type the implementer has specified. + +#### Associated Types Versus Generics + +When we implemented the `Iterator` trait on the `Counter` struct in Listing +13-6, we specified that the `Item` type was `u32`: + +```rust,ignore +impl Iterator for Counter { + type Item = u32; + + fn next(&mut self) -> Option { +``` + +This feels similar to generics. So why isn't the `Iterator` trait defined as +shown in Listing 19-21? ```rust -trait Foo { - fn foo(&self) -> Bar; -} - -impl Foo for i32 { - fn foo(&self) -> String { - self.to_string() - } +pub trait Iterator { + fn next(&mut self) -> Option; } ``` -But there's one big difference: with the second definition, we could also -implement `Foo for i32`, or anything else. In other words, with a trait -that has a generic parameter, we can implement that trait for a type multiple -times, changing the parameters each time. But with associated types, we can't; -we can only define it one time: it's not actually generic. +Listing 19-21: A hypothetical definition of the +`Iterator` trait using generics -There's another benefit to associated types: when using the trait, since there's -only one possible implementation, you end up with a lot less syntax. This is -easier with some code: +The difference is that with the definition in Listing 19-21, we could also +implement `Iterator for Counter`, or any other type as well, so that +we'd have multiple implementations of `Iterator` for `Counter`. In other words, +when a trait has a generic parameter, we can implement that trait for a type +multiple times, changing the generic type parameters' concrete types each time. +Then when we use the `next` method on `Counter`, we'd have to provide type +annotations to indicate which implementation of `Iterator` we wanted to use. + +With associated types, we can't implement a trait on a type multiple times. +Using the actual definition of `Iterator` from Listing 19-20, we can only +choose once what the type of `Item` will be, since there can only be one `impl +Iterator for Counter`. We don't have to specify that we want an iterator of +`u32` values everywhere that we call `next` on `Counter`. + +The benefit of not having to specify generic type parameters when a trait uses +associated types shows up in another way as well. Consider the two traits +defined in Listing 19-22. Both are defining a trait having to do with a graph +structure that contains nodes of some type and edges of some type. `GGraph` is +defined using generics, and `AGraph` is defined using associated types: ```rust -// a generic graph trait GGraph { // methods would go here } -// an associated graph trait AGraph { type Node; type Edge; @@ -70,24 +92,104 @@ trait AGraph { } ``` -Let's say we wanted to compute the distance between two nodes in the graph. -With the generic graph, you'd have to write this: +Listing 19-22: Two graph trait definitions, `GGraph` +using generics and `AGraph` using associated types for `Node` and `Edge` -```rust,ignore -fn distance>(graph: &G, start: &N, end: &N) -> u32 { ... } +Let's say we wanted to implement a function that computes the distance between +two nodes in any types that implement the graph trait. With the `GGraph` trait +defined using generics, our `distance` function signature would have to look +like Listing 19-23: + +```rust +# trait GGraph {} +# +fn distance>(graph: &G, start: &N, end: &N) -> u32 { +# 0 +} ``` -Even though `distance` doesn't need to know the types of the edges, we're -forced to declare an `E` parameter, because we need to to use `Graph`. But with -the associated type version: +Listing 19-23: The signature of a `distance` function +that uses the trait `GGraph` and has to specify all the generic +parameters -```rust,ignore -fn distance(graph: &G, start: &G::Node, end: &G::Node) -> u32 { ... } +Our function would need to specify the generic type parameters `N`, `E`, and +`G`, where `G` is bound by the trait `GGraph` that has type `N` as its `Node` +type and type `E` as its `Edge` type. Even though `distance` doesn't need to +know the types of the edges, we're forced to declare an `E` parameter, because +we need to to use the `GGraph` trait and that requires specifying the type for +`Edge`. + +Contrast with the definition of `distance` in Listing 19-24 that uses the +`AGraph` trait from Listing 19-22 with associated types: + +```rust +# trait AGraph { +# type Node; +# type Edge; +# } +# +fn distance(graph: &G, start: &G::Node, end: &G::Node) -> u32 { +# 0 +} ``` -This is much cleaner. +Listing 19-24: The signature of a `distance` function +that uses the trait `AGraph` and the associated type `Node` -## Operator overloading and default type parameters +This is much cleaner. We only need to have one generic type parameter, `G`, +with the trait bound `AGraph`. Since `distance` doesn't use the `Edge` type at +all, it doesn't need to be specified anywhere. To use the `Node` type +associated with `AGraph`, we can specify `G::Node`. + +#### Trait Objects with Associated Types + +You may have been wondering why we didn't use a trait object in the `distance` +functions in Listing 19-23 and Listing 19-24. The signature for the `distance` +function using the generic `GGraph` trait does get a bit more concise using a +trait object: + +```rust +# trait GGraph {} +# +fn distance(graph: &GGraph, start: &N, end: &N) -> u32 { +# 0 +} +``` + +This might be a more fair comparison to Listing 19-24. Specifying the `Edge` +type is still required, though, which means Listing 19-24 is still preferable +since we don't have to specify something we don't use. + +It's not possible to change Listing 19-24 to use a trait object for the graph, +since then there would be no way to refer to the `AGraph` trait's associated +type. + +It is possible in general to use trait objects of traits that have associated +types, though; Listing 19-25 shows a function named `traverse` that doesn't +need to use the trait's associated types in other arguments. We do, however, +have to specify the concrete types for the associated types in this case. Here, +we've chosen to accept types that implement the `AGraph` trait with the +concrete type of `usize` as their `Node` type and a tuple of two `usize` values +for their `Edge` type: + +```rust +# trait AGraph { +# type Node; +# type Edge; +# } +# +fn traverse(graph: &AGraph) {} +``` + +While trait objects mean that we don't need to know the concrete type of the +`graph` parameter at compile time, we do need to constrain the use of the +`AGraph` trait in the `traverse` function by the concrete types of the +associated types. If we didn’t provide this constraint, Rust wouldn't be able +to figure out which `impl` to match this trait object to. + +TODO: I basically copied the last sentence from the old book but i dont really understand it /Carol + +### Operator overloading and default type parameters We can use traits in Rust to overload certain operators. Rust does not allow you to create your own operators, or overload arbitrary operators: only the operations listed @@ -195,7 +297,7 @@ because our existing users won't have written down a type parameter, if we want to add a type parameter to an existing trait, giving it a default will let us not break that code. -## Fully qualified syntax +### Fully qualified syntax Sometimes, methods can have the same names. Consider this code: @@ -340,7 +442,7 @@ fn main() { Using this syntax lets you call the trait method instead of the inherent one. -## Super traits +### Super traits Sometimes, you may want a trait to be able to rely on another trait existing. For example, let's say that you have a `Foo` trait and a `Bar` trait, but you @@ -393,7 +495,7 @@ trait Bar: Foo { This works fine. -## Coherence +### Coherence Finally, traits have a concept called 'coherence'. This governs exactly who is allowed to implement a trait. In short: @@ -444,7 +546,7 @@ what if a new release of `foo` comes out and implements `B` for `A` themselves? These problems are not insurmountable, of course; we could determine some kind of complex precedent rules to determine which `impl` 'wins' and works. -## The newtype pattern +### The newtype pattern There is a way to get around this, though. We call it the 'newtype pattern'. You create a new type that's a thin wrapper around the type you want to