about summary refs log tree commit diff
diff options
context:
space:
mode:
authorBrian Anderson <banderson@mozilla.com>2012-09-23 21:07:01 -0700
committerBrian Anderson <banderson@mozilla.com>2012-09-23 21:09:44 -0700
commit1b1aea8e0e3f1035aebda80c20a59d0d4d1704b8 (patch)
treec3bd283353cc0c442499b17b8a1f9bff4be1e854
parent8baed1f25f5fcec221dfd93e0c29b723725bcabe (diff)
downloadrust-1b1aea8e0e3f1035aebda80c20a59d0d4d1704b8.tar.gz
rust-1b1aea8e0e3f1035aebda80c20a59d0d4d1704b8.zip
tutorial: Begin trying to improve the generics section
-rw-r--r--doc/tutorial.md130
1 files changed, 76 insertions, 54 deletions
diff --git a/doc/tutorial.md b/doc/tutorial.md
index 8a5d80038e4..fd19e7ef004 100644
--- a/doc/tutorial.md
+++ b/doc/tutorial.md
@@ -1528,12 +1528,9 @@ fn contains(v: &[int], elt: int) -> bool {
 
 # Generics
 
-## Generic functions
-
 Throughout this tutorial, we've been defining functions that act only on
-single data types. It's a burden to define such functions again and again for
-every type they apply to. Thus, Rust allows functions and datatypes to have
-type parameters.
+single data types. With type parameters we can also define functions that
+may be invoked on multiple types.
 
 ~~~~
 fn map<T, U>(vector: &[T], function: fn(v: &T) -> U) -> ~[U] {
@@ -1545,9 +1542,10 @@ fn map<T, U>(vector: &[T], function: fn(v: &T) -> U) -> ~[U] {
 }
 ~~~~
 
-When defined with type parameters, this function can be applied to any
-type of vector, as long as the type of `function`'s argument and the
-type of the vector's content agree with each other.
+When defined with type parameters, as denoted by `<T, U>`, this
+function can be applied to any type of vector, as long as the type of
+`function`'s argument and the type of the vector's content agree with
+each other.
 
 Inside a generic function, the names of the type parameters
 (capitalized by convention) stand for opaque types. You can't look
@@ -1558,11 +1556,12 @@ parameter `function()` is supplied with a pointer to a value of type
 function works with the broadest set of types possible, since some
 types are expensive or illegal to copy and pass by value.
 
-## Generic datatypes
-
 Generic `type`, `struct`, and `enum` declarations follow the same pattern:
 
 ~~~~
+# use std::map::HashMap;
+type Set<T> = HashMap<T, ()>;
+
 struct Stack<T> {
     elements: ~[mut T]
 }
@@ -1573,89 +1572,112 @@ enum Maybe<T> {
 }
 ~~~~
 
-These declarations produce valid types like `Stack<u8>` and `Maybe<int>`.
+These declarations produce valid types like `Set<int>`, `Stack<int>`
+and `Maybe<int>`.
 
-## Kinds
+## Traits
 
 Perhaps surprisingly, the 'copy' (duplicate) operation is not defined
-for all Rust types. Resource types (classes with destructors) cannot be
-copied, and neither can any type whose copying would require copying a
-resource (such as records or unique boxes containing a resource).
+for all Rust types. Types with user-defined destructors cannot be
+copied, and neither can types that own other types containing
+destructors.
+
+~~~
+// Instances of this struct can't be copied, either implicitly
+// or with the `copy` keyword
+struct NotCopyable {
+    foo: int,
+
+    drop { }
+}
+
+// This owned box containing a NotCopyable is also not copyable
+let not_copyable_box = ~NotCopyable { foo: 0 };
+~~~
 
 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,
-unless you explicitly declare that type parameter to have copyable
-'kind'. A kind is a type of type.
+unless you explicitly declare that type parameter to have the
+_trait_ for copying, called `Copy`.
 
 ~~~~ {.ignore}
 // This does not compile
-fn head_bad<T>(v: ~[T]) -> T { v[0] }
+fn head_bad<T>(v: ~[T]) -> T {
+    copy v[0] // Elements of type T aren't copyable
+}
+~~~~
+
+~~~~
 // This does
-fn head<T: Copy>(v: ~[T]) -> T { v[0] }
+fn head<T: Copy>(v: ~[T]) -> T {
+   copy v[0]
+}
 ~~~~
 
 When instantiating a generic function, you can only instantiate it
-with types that fit its kinds. So you could not apply `head` to a
-resource type. Rust has several kinds that can be used as type bounds:
-
-* `Copy` - Copyable types. All types are copyable unless they
-  are classes with destructors or otherwise contain
-  classes with destructors.
-* `Send` - Sendable types. All types are sendable unless they
-  contain shared boxes, closures, or other local-heap-allocated
-  types.
-* `Const` - Constant types. These are types that do not contain
-  mutable fields nor shared boxes.
-
-> ***Note:*** Rust type kinds are syntactically very similar to
-> [traits](#traits) when used as type bounds, and can be
-> conveniently thought of as built-in traits. In the future type
-> kinds will actually be traits that the compiler has special
-> knowledge about.
-
-# Traits
+with types that implement the correct traits. So you could not apply
+`head` to a type with a destructor.
+
+While most traits can be defined and implemented by user code, three
+traits are derived for all applicable types by the compiler, and may
+not be overridden:
+
+* `Copy` - Types that can be copied, either implicitly, or using the
+  `copy` expression. All types are copyable unless they are classes
+  with destructors or otherwise contain classes with destructors.
+
+* `Send` - Sendable (owned) types. All types are sendable unless they
+  contain managed boxes, managed closures, or otherwise managed
+  types. Sendable types may or may not be copyable.
+
+* `Const` - Constant (immutable) types. These are types that do not contain
+  mutable fields.
+
+> ***Note:*** These three traits were referred to as 'kinds' in earlier
+> iterations of the language, and often still are.
 
 Traits are Rust's take on value polymorphism—the thing that
 object-oriented languages tend to solve with methods and inheritance.
 For example, writing a function that can operate on multiple types of
 collections.
 
-> ***Note:*** This feature is very new, and will need a few extensions to be
-> applicable to more advanced use cases.
-
-## Declaration
+## Declaring and implementing traits
 
-A trait consists of a set of methods. A method is a function that
+A trait consists of a set of methods, or may be empty, as is the case
+with `Copy`, `Send`, and `Const`. A method is a function that
 can be applied to a `self` value and a number of arguments, using the
 dot notation: `self.foo(arg1, arg2)`.
 
-For example, we could declare the trait `to_str` for things that
-can be converted to a string, with a single method of the same name:
+For example, we could declare the trait `Stringable` for things that
+can be converted to a string, with a single method:
 
 ~~~~
 trait ToStr {
-    fn to_str() -> ~str;
+    fn to_str(self) -> ~str;
 }
 ~~~~
 
-## Implementation
-
 To actually implement a trait for a given type, the `impl` form
-is used. This defines implementations of `to_str` for the `int` and
+is used. This defines implementations of `ToStr` for the `int` and
 `~str` types.
 
 ~~~~
-# trait ToStr { fn to_str() -> ~str; }
+# // FIXME: This example is no good because you can't actually
+# // implement your own .to_str for int and ~str
+# trait ToStr { fn to_str(self) -> ~str; }
 impl int: ToStr {
-    fn to_str() -> ~str { int::to_str(self, 10u) }
+    fn to_str(self) -> ~str { int::to_str(self, 10u) }
 }
 impl ~str: ToStr {
-    fn to_str() -> ~str { self }
+    fn to_str(self) -> ~str { self }
 }
+
+# //1.to_str();
+# //(~"foo").to_str();
 ~~~~
 
-Given these, we may call `1.to_str()` to get `~"1"`, or
-`(~"foo").to_str()` to get `~"foo"` again. This is basically a form of
+Given these, we may call `1.to_str()` to get `"1"`, or
+`(~"foo").to_str()` to get `"foo"` again. This is basically a form of
 static overloading—when the Rust compiler sees the `to_str` method
 call, it looks for an implementation that matches the type with a
 method that matches the name, and simply calls that.