From 62b74d9890ed92e55166088c0bc128d6e72e2810 Mon Sep 17 00:00:00 2001 From: Lindsey Kuper Date: Thu, 2 Jan 2014 00:22:50 -0500 Subject: [PATCH] Write up default methods for the tutorial. --- doc/tutorial.md | 153 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 111 insertions(+), 42 deletions(-) diff --git a/doc/tutorial.md b/doc/tutorial.md index 0eb228d82059d..03370bd6bd006 100644 --- a/doc/tutorial.md +++ b/doc/tutorial.md @@ -2035,28 +2035,30 @@ C++ templates. ## Traits -Within a generic function the operations available on generic types -are very limited. After all, since the function doesn't know what -types it is operating on, it can't safely modify or query their -values. This is where _traits_ come into play. Traits are Rust's most -powerful tool for writing polymorphic code. Java developers will see -them as similar to Java interfaces, and Haskellers will notice their -similarities to type classes. Rust's traits are a form of *bounded -polymorphism*: a trait is a way of limiting the set of possible types -that a type parameter could refer to. - -As motivation, let us consider copying in Rust. -The `clone` method is not defined for all Rust types. -One reason is user-defined destructors: -copying a type that has a destructor -could result in the destructor running multiple times. -Therefore, types with destructors cannot be copied -unless you explicitly implement `Clone` for them. +Within a generic function -- that is, a function parameterized by a +type parameter, say, `T` -- the operations we can do on arguments of +type `T` are quite limited. After all, since we don't know what type +`T` will be instantiated with, we can't safely modify or query values +of type `T`. This is where _traits_ come into play. Traits are Rust's +most powerful tool for writing polymorphic code. Java developers will +see them as similar to Java interfaces, and Haskellers will notice +their similarities to type classes. Rust's traits give us a way to +express *bounded polymorphism*: by limiting the set of possible types +that a type parameter could refer to, they expand the number of +operations we can safely perform on arguments of that type. + +As motivation, let us consider copying of values in Rust. The `clone` +method is not defined for values of every type. One reason is +user-defined destructors: copying a value of a type that has a +destructor could result in the destructor running multiple times. +Therefore, values of types that have destructors cannot be copied +unless we explicitly implement `clone` for them. This complicates handling of generic functions. -If you have a type parameter `T`, can you copy values of that type? -In Rust, you can't, -and if you try to run the following code the compiler will complain. +If we have a function with a type parameter `T`, +can we copy values of type `T` inside that function? +In Rust, we can't, +and if we try to run the following code the compiler will complain. ~~~~ {.xfail-test} // This does not compile @@ -2066,11 +2068,10 @@ fn head_bad(v: &[T]) -> T { ~~~~ However, we can tell the compiler -that the `head` function is only for copyable types: -that is, those that implement the `Clone` trait. -In that case, -we can explicitly create a second copy of the value we are returning -using the `clone` keyword: +that the `head` function is only for copyable types. +In Rust, copyable types are those that _implement the `Clone` trait_. +We can then explicitly create a second copy of the value we are returning +by calling the `clone` method: ~~~~ // This does @@ -2079,12 +2080,13 @@ fn head(v: &[T]) -> T { } ~~~~ -This says that we can call `head` on any type `T` -as long as that type implements the `Clone` trait. +The bounded type parameter `T: Clone` says that `head` is polymorphic +over any type `T`, so long as there is an implementation of the +`Clone` trait for that type. When instantiating a generic function, -you can only instantiate it with types +we can only instantiate it with types that implement the correct trait, -so you could not apply `head` to a type +so we could not apply `head` to a vector whose elements are of some type that does not implement `Clone`. While most traits can be defined and implemented by user code, @@ -2110,7 +2112,7 @@ have the `'static` lifetime. > iterations of the language, and often still are. Additionally, the `Drop` trait is used to define destructors. This -trait defines one method called `drop`, which is automatically +trait provides one method called `drop`, which is automatically called when a value of the type that implements this trait is destroyed, either because the value went out of scope or because the garbage collector reclaimed it. @@ -2134,11 +2136,10 @@ may call it. ## Declaring and implementing traits -A trait consists of a set of methods without bodies, -or may be empty, as is the case with `Send` and `Freeze`. +At its simplest, a trait is a set of zero or more _method signatures_. For example, we could declare the trait `Printable` for things that can be printed to the console, -with a single method: +with a single method signature: ~~~~ trait Printable { @@ -2146,31 +2147,99 @@ trait Printable { } ~~~~ -Traits may be implemented for specific types with [impls]. An impl -that implements a trait includes the name of the trait at the start of -the definition, as in the following impls of `Printable` for `int` -and `~str`. +We say that the `Printable` trait _provides_ a `print` method with the +given signature. This means that we can call `print` on an argument +of any type that implements the `Printable` trait. + +Rust's built-in `Send` and `Freeze` types are examples of traits that +don't provide any methods. + +Traits may be implemented for specific types with [impls]. An impl for +a particular trait gives an implementation of the methods that that +trait provides. For instance, the following the following impls of +`Printable` for `int` and `~str` give implementations of the `print` +method. [impls]: #methods ~~~~ # trait Printable { fn print(&self); } impl Printable for int { - fn print(&self) { println!("{}", *self) } + fn print(&self) { println!("{:?}", *self) } +} + +impl Printable for ~str { + fn print(&self) { println(*self) } +} + +# 1.print(); +# (~"foo").print(); +~~~~ + +Methods defined in an impl for a trait may be called just like +any other method, using dot notation, as in `1.print()`. + +## Default method implementations in trait definitions + +Sometimes, a method that a trait provides will have the same +implementation for most or all of the types that implement that trait. +For instance, suppose that we wanted `bool`s and `float`s to be +printable, and that we wanted the implementation of `print` for those +types to be exactly as it is for `int`, above: + +~~~~ +impl Printable for float { + fn print(&self) { println!("{:?}", *self) } +} + +impl Printable for bool { + fn print(&self) { println!("{:?}", *self) } } +# true.print(); +# 3.14159.print(); +~~~~ + +This works fine, but we've now repeated the same definition of `print` +in three places. Instead of doing that, we can simply include the +definition of `print` right in the trait definition, instead of just +giving its signature. That is, we can write the following: + +~~~~ +trait Printable { + // Default method implementation + fn print(&self) { println!("{:?}", *self) } +} + +impl Printable for int {} + impl Printable for ~str { fn print(&self) { println(*self) } } +impl Printable for bool {} + +impl Printable for float {} + # 1.print(); # (~"foo").print(); +# true.print(); +# 3.14159.print(); ~~~~ -Methods defined in an implementation of a trait may be called just like -any other method, using dot notation, as in `1.print()`. Traits may -themselves contain type parameters. A trait for generalized sequence -types might look like the following: +Here, the impls of `Printable` for `int`, `bool`, and `float` don't +need to provide an implementation of `print`, because in the absence +of a specific implementation, Rust just uses the _default method_ +provided in the trait definition. Depending on the trait, default +methods can save a great deal of boilerplate code from having to be +written in impls. Of course, individual impls can still override the +default method for `print`, as is being done above in the impl for +`~str`. + +## Type-parameterized traits + +Traits may be parameterized by type variables. For example, a trait +for generalized sequence types might look like the following: ~~~~ trait Seq {