]> git.lizzy.rs Git - rust.git/commitdiff
Write up default methods for the tutorial.
authorLindsey Kuper <lindsey@composition.al>
Thu, 2 Jan 2014 05:22:50 +0000 (00:22 -0500)
committerLindsey Kuper <lindsey@composition.al>
Mon, 6 Jan 2014 03:03:12 +0000 (22:03 -0500)
doc/tutorial.md

index 0eb228d82059d3247b3a49c4051c608a2ec77811..03370bd6bd0064761f62be85a24d216a2424aadc 100644 (file)
@@ -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<T>(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<T: Clone>(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<T> {