tutorial: swap order of "Traits" and "Modules and Crates" sections.
This puts "Traits" next to "Generics", as requested by issue #3397. Closes #3397.
This commit is contained in:
parent
53ce42dc4f
commit
62ab9d70f4
394
doc/tutorial.md
394
doc/tutorial.md
@ -1605,6 +1605,203 @@ resource type. Rust has several kinds that can be used as type bounds:
|
||||
> kinds will actually be traits that the compiler has special
|
||||
> knowledge about.
|
||||
|
||||
# Traits
|
||||
|
||||
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
|
||||
|
||||
A trait consists of a set of methods. 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:
|
||||
|
||||
~~~~
|
||||
trait to_str {
|
||||
fn to_str() -> ~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
|
||||
`~str` types.
|
||||
|
||||
~~~~
|
||||
# trait to_str { fn to_str() -> ~str; }
|
||||
impl int: to_str {
|
||||
fn to_str() -> ~str { int::to_str(self, 10u) }
|
||||
}
|
||||
impl ~str: to_str {
|
||||
fn to_str() -> ~str { self }
|
||||
}
|
||||
~~~~
|
||||
|
||||
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.
|
||||
|
||||
## Bounded type parameters
|
||||
|
||||
The useful thing about value polymorphism is that it does not have to
|
||||
be static. If object-oriented languages only let you call a method on
|
||||
an object when they knew exactly which sub-type it had, that would not
|
||||
get you very far. To be able to call methods on types that aren't
|
||||
known at compile time, it is possible to specify 'bounds' for type
|
||||
parameters.
|
||||
|
||||
~~~~
|
||||
# trait to_str { fn to_str() -> ~str; }
|
||||
fn comma_sep<T: to_str>(elts: ~[T]) -> ~str {
|
||||
let mut result = ~"", first = true;
|
||||
for elts.each |elt| {
|
||||
if first { first = false; }
|
||||
else { result += ~", "; }
|
||||
result += elt.to_str();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
~~~~
|
||||
|
||||
The syntax for this is similar to the syntax for specifying that a
|
||||
parameter type has to be copyable (which is, in principle, another
|
||||
kind of bound). By declaring `T` as conforming to the `to_str`
|
||||
trait, it becomes possible to call methods from that trait on
|
||||
values of that type inside the function. It will also cause a
|
||||
compile-time error when anyone tries to call `comma_sep` on an array
|
||||
whose element type does not have a `to_str` implementation in scope.
|
||||
|
||||
## Polymorphic traits
|
||||
|
||||
Traits may contain type parameters. A trait for
|
||||
generalized sequence types is:
|
||||
|
||||
~~~~
|
||||
trait seq<T> {
|
||||
fn len() -> uint;
|
||||
fn iter(fn(T));
|
||||
}
|
||||
impl<T> ~[T]: seq<T> {
|
||||
fn len() -> uint { vec::len(self) }
|
||||
fn iter(b: fn(T)) {
|
||||
for self.each |elt| { b(elt); }
|
||||
}
|
||||
}
|
||||
~~~~
|
||||
|
||||
The implementation has to explicitly declare the type
|
||||
parameter that it binds, `T`, before using it to specify its trait type. Rust requires this declaration because the `impl` could also, for example, specify an implementation of `seq<int>`. The trait type -- appearing after the colon in the `impl` -- *refers* to a type, rather than defining one.
|
||||
|
||||
The type parameters bound by a trait are in scope in each of the
|
||||
method declarations. So, re-declaring the type parameter
|
||||
`T` as an explicit type parameter for `len` -- in either the trait or
|
||||
the impl -- would be a compile-time error.
|
||||
|
||||
## The `self` type in traits
|
||||
|
||||
In a trait, `self` is a special type that you can think of as a
|
||||
type parameter. An implementation of the trait for any given type
|
||||
`T` replaces the `self` type parameter with `T`. The following
|
||||
trait describes types that support an equality operation:
|
||||
|
||||
~~~~
|
||||
trait eq {
|
||||
fn equals(&&other: self) -> bool;
|
||||
}
|
||||
|
||||
impl int: eq {
|
||||
fn equals(&&other: int) -> bool { other == self }
|
||||
}
|
||||
~~~~
|
||||
|
||||
Notice that `equals` takes an `int` argument, rather than a `self` argument, in
|
||||
an implementation for type `int`.
|
||||
|
||||
## Casting to a trait type
|
||||
|
||||
The above allows us to define functions that polymorphically act on
|
||||
values of *an* unknown type that conforms to a given trait.
|
||||
However, consider this function:
|
||||
|
||||
~~~~
|
||||
# type circle = int; type rectangle = int;
|
||||
# trait drawable { fn draw(); }
|
||||
# impl int: drawable { fn draw() {} }
|
||||
# fn new_circle() -> int { 1 }
|
||||
fn draw_all<T: drawable>(shapes: ~[T]) {
|
||||
for shapes.each |shape| { shape.draw(); }
|
||||
}
|
||||
# let c: circle = new_circle();
|
||||
# draw_all(~[c]);
|
||||
~~~~
|
||||
|
||||
You can call that on an array of circles, or an array of squares
|
||||
(assuming those have suitable `drawable` traits defined), but not
|
||||
on an array containing both circles and squares.
|
||||
|
||||
When this is needed, a trait name can be used as a type, causing
|
||||
the function to be written simply like this:
|
||||
|
||||
~~~~
|
||||
# trait drawable { fn draw(); }
|
||||
fn draw_all(shapes: ~[drawable]) {
|
||||
for shapes.each |shape| { shape.draw(); }
|
||||
}
|
||||
~~~~
|
||||
|
||||
There is no type parameter anymore (since there isn't a single type
|
||||
that we're calling the function on). Instead, the `drawable` type is
|
||||
used to refer to a type that is a reference-counted box containing a
|
||||
value for which a `drawable` implementation exists, combined with
|
||||
information on where to find the methods for this implementation. This
|
||||
is very similar to the 'vtables' used in most object-oriented
|
||||
languages.
|
||||
|
||||
To construct such a value, you use the `as` operator to cast a value
|
||||
to a trait type:
|
||||
|
||||
~~~~
|
||||
# type circle = int; type rectangle = int;
|
||||
# trait drawable { fn draw(); }
|
||||
# impl int: drawable { fn draw() {} }
|
||||
# fn new_circle() -> int { 1 }
|
||||
# fn new_rectangle() -> int { 2 }
|
||||
# fn draw_all(shapes: ~[drawable]) {}
|
||||
let c: circle = new_circle();
|
||||
let r: rectangle = new_rectangle();
|
||||
draw_all(~[c as drawable, r as drawable]);
|
||||
~~~~
|
||||
|
||||
This will store the value into a box, along with information about the
|
||||
implementation (which is looked up in the scope of the cast). The
|
||||
`drawable` type simply refers to such boxes, and calling methods on it
|
||||
always works, no matter what implementations are in scope.
|
||||
|
||||
Note that the allocation of a box is somewhat more expensive than
|
||||
simply using a type parameter and passing in the value as-is, and much
|
||||
more expensive than statically resolved method calls.
|
||||
|
||||
## Trait-less implementations
|
||||
|
||||
If you only intend to use an implementation for static overloading,
|
||||
and there is no trait available that it conforms to, you are free
|
||||
to leave off the type after the colon. However, this is only possible when you
|
||||
are defining an implementation in the same module as the receiver
|
||||
type, and the receiver type is a named type (i.e., an enum or a
|
||||
class); [single-variant enums](#single_variant_enum) are a common
|
||||
choice.
|
||||
|
||||
# Modules and crates
|
||||
|
||||
The Rust namespace is divided into modules. Each source file starts
|
||||
@ -1872,203 +2069,6 @@ This makes it possible to rebind a variable without actually mutating
|
||||
it, which is mostly useful for destructuring (which can rebind, but
|
||||
not assign).
|
||||
|
||||
# Traits
|
||||
|
||||
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
|
||||
|
||||
A trait consists of a set of methods. 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:
|
||||
|
||||
~~~~
|
||||
trait to_str {
|
||||
fn to_str() -> ~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
|
||||
`~str` types.
|
||||
|
||||
~~~~
|
||||
# trait to_str { fn to_str() -> ~str; }
|
||||
impl int: to_str {
|
||||
fn to_str() -> ~str { int::to_str(self, 10u) }
|
||||
}
|
||||
impl ~str: to_str {
|
||||
fn to_str() -> ~str { self }
|
||||
}
|
||||
~~~~
|
||||
|
||||
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.
|
||||
|
||||
## Bounded type parameters
|
||||
|
||||
The useful thing about value polymorphism is that it does not have to
|
||||
be static. If object-oriented languages only let you call a method on
|
||||
an object when they knew exactly which sub-type it had, that would not
|
||||
get you very far. To be able to call methods on types that aren't
|
||||
known at compile time, it is possible to specify 'bounds' for type
|
||||
parameters.
|
||||
|
||||
~~~~
|
||||
# trait to_str { fn to_str() -> ~str; }
|
||||
fn comma_sep<T: to_str>(elts: ~[T]) -> ~str {
|
||||
let mut result = ~"", first = true;
|
||||
for elts.each |elt| {
|
||||
if first { first = false; }
|
||||
else { result += ~", "; }
|
||||
result += elt.to_str();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
~~~~
|
||||
|
||||
The syntax for this is similar to the syntax for specifying that a
|
||||
parameter type has to be copyable (which is, in principle, another
|
||||
kind of bound). By declaring `T` as conforming to the `to_str`
|
||||
trait, it becomes possible to call methods from that trait on
|
||||
values of that type inside the function. It will also cause a
|
||||
compile-time error when anyone tries to call `comma_sep` on an array
|
||||
whose element type does not have a `to_str` implementation in scope.
|
||||
|
||||
## Polymorphic traits
|
||||
|
||||
Traits may contain type parameters. A trait for
|
||||
generalized sequence types is:
|
||||
|
||||
~~~~
|
||||
trait seq<T> {
|
||||
fn len() -> uint;
|
||||
fn iter(fn(T));
|
||||
}
|
||||
impl<T> ~[T]: seq<T> {
|
||||
fn len() -> uint { vec::len(self) }
|
||||
fn iter(b: fn(T)) {
|
||||
for self.each |elt| { b(elt); }
|
||||
}
|
||||
}
|
||||
~~~~
|
||||
|
||||
The implementation has to explicitly declare the type
|
||||
parameter that it binds, `T`, before using it to specify its trait type. Rust requires this declaration because the `impl` could also, for example, specify an implementation of `seq<int>`. The trait type -- appearing after the colon in the `impl` -- *refers* to a type, rather than defining one.
|
||||
|
||||
The type parameters bound by a trait are in scope in each of the
|
||||
method declarations. So, re-declaring the type parameter
|
||||
`T` as an explicit type parameter for `len` -- in either the trait or
|
||||
the impl -- would be a compile-time error.
|
||||
|
||||
## The `self` type in traits
|
||||
|
||||
In a trait, `self` is a special type that you can think of as a
|
||||
type parameter. An implementation of the trait for any given type
|
||||
`T` replaces the `self` type parameter with `T`. The following
|
||||
trait describes types that support an equality operation:
|
||||
|
||||
~~~~
|
||||
trait eq {
|
||||
fn equals(&&other: self) -> bool;
|
||||
}
|
||||
|
||||
impl int: eq {
|
||||
fn equals(&&other: int) -> bool { other == self }
|
||||
}
|
||||
~~~~
|
||||
|
||||
Notice that `equals` takes an `int` argument, rather than a `self` argument, in
|
||||
an implementation for type `int`.
|
||||
|
||||
## Casting to a trait type
|
||||
|
||||
The above allows us to define functions that polymorphically act on
|
||||
values of *an* unknown type that conforms to a given trait.
|
||||
However, consider this function:
|
||||
|
||||
~~~~
|
||||
# type circle = int; type rectangle = int;
|
||||
# trait drawable { fn draw(); }
|
||||
# impl int: drawable { fn draw() {} }
|
||||
# fn new_circle() -> int { 1 }
|
||||
fn draw_all<T: drawable>(shapes: ~[T]) {
|
||||
for shapes.each |shape| { shape.draw(); }
|
||||
}
|
||||
# let c: circle = new_circle();
|
||||
# draw_all(~[c]);
|
||||
~~~~
|
||||
|
||||
You can call that on an array of circles, or an array of squares
|
||||
(assuming those have suitable `drawable` traits defined), but not
|
||||
on an array containing both circles and squares.
|
||||
|
||||
When this is needed, a trait name can be used as a type, causing
|
||||
the function to be written simply like this:
|
||||
|
||||
~~~~
|
||||
# trait drawable { fn draw(); }
|
||||
fn draw_all(shapes: ~[drawable]) {
|
||||
for shapes.each |shape| { shape.draw(); }
|
||||
}
|
||||
~~~~
|
||||
|
||||
There is no type parameter anymore (since there isn't a single type
|
||||
that we're calling the function on). Instead, the `drawable` type is
|
||||
used to refer to a type that is a reference-counted box containing a
|
||||
value for which a `drawable` implementation exists, combined with
|
||||
information on where to find the methods for this implementation. This
|
||||
is very similar to the 'vtables' used in most object-oriented
|
||||
languages.
|
||||
|
||||
To construct such a value, you use the `as` operator to cast a value
|
||||
to a trait type:
|
||||
|
||||
~~~~
|
||||
# type circle = int; type rectangle = int;
|
||||
# trait drawable { fn draw(); }
|
||||
# impl int: drawable { fn draw() {} }
|
||||
# fn new_circle() -> int { 1 }
|
||||
# fn new_rectangle() -> int { 2 }
|
||||
# fn draw_all(shapes: ~[drawable]) {}
|
||||
let c: circle = new_circle();
|
||||
let r: rectangle = new_rectangle();
|
||||
draw_all(~[c as drawable, r as drawable]);
|
||||
~~~~
|
||||
|
||||
This will store the value into a box, along with information about the
|
||||
implementation (which is looked up in the scope of the cast). The
|
||||
`drawable` type simply refers to such boxes, and calling methods on it
|
||||
always works, no matter what implementations are in scope.
|
||||
|
||||
Note that the allocation of a box is somewhat more expensive than
|
||||
simply using a type parameter and passing in the value as-is, and much
|
||||
more expensive than statically resolved method calls.
|
||||
|
||||
## Trait-less implementations
|
||||
|
||||
If you only intend to use an implementation for static overloading,
|
||||
and there is no trait available that it conforms to, you are free
|
||||
to leave off the type after the colon. However, this is only possible when you
|
||||
are defining an implementation in the same module as the receiver
|
||||
type, and the receiver type is a named type (i.e., an enum or a
|
||||
class); [single-variant enums](#single_variant_enum) are a common
|
||||
choice.
|
||||
|
||||
# Tasks
|
||||
|
||||
Rust supports a system of lightweight tasks, similar to what is found
|
||||
|
Loading…
Reference in New Issue
Block a user