diff options
| author | Brian Anderson <banderson@mozilla.com> | 2012-09-23 21:07:01 -0700 | 
|---|---|---|
| committer | Brian Anderson <banderson@mozilla.com> | 2012-09-23 21:09:44 -0700 | 
| commit | 1b1aea8e0e3f1035aebda80c20a59d0d4d1704b8 (patch) | |
| tree | c3bd283353cc0c442499b17b8a1f9bff4be1e854 | |
| parent | 8baed1f25f5fcec221dfd93e0c29b723725bcabe (diff) | |
| download | rust-1b1aea8e0e3f1035aebda80c20a59d0d4d1704b8.tar.gz rust-1b1aea8e0e3f1035aebda80c20a59d0d4d1704b8.zip | |
tutorial: Begin trying to improve the generics section
| -rw-r--r-- | doc/tutorial.md | 130 | 
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. | 
