Skip to content

Commit e8a33c9

Browse files
committed
---
yaml --- r: 7197 b: refs/heads/master c: ca72a83 h: refs/heads/master i: 7195: 6bb7507 v: v3
1 parent 700460b commit e8a33c9

File tree

3 files changed

+185
-1
lines changed

3 files changed

+185
-1
lines changed

[refs]

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
---
2-
refs/heads/master: 0f72c53fdfb912319260aa8462ae20f07599631d
2+
refs/heads/master: ca72a8300b2781b8267e0afffcc5a7692c25d366

trunk/doc/tutorial/iface.md

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# Interfaces
2+
3+
Interfaces are Rust's take on value polymorphism—the thing that
4+
object-oriented languages tend to solve with methods and inheritance.
5+
For example, writing a function that can operate on multiple types of
6+
collections.
7+
8+
NOTE: This feature is very new, and will need a few extensions to be
9+
applicable to more advanced use cases.
10+
11+
## Declaration
12+
13+
An interface consists of a set of methods. A method is a function that
14+
can be applied to a `self` value and a number of arguments, using the
15+
dot notation: `self.foo(arg1, arg2)`.
16+
17+
For example, we could declare the interface `to_str` for things that
18+
can be converted to a string, with a single method of the same name:
19+
20+
iface to_str {
21+
fn to_str() -> str;
22+
}
23+
24+
## Implementation
25+
26+
To actually implement an interface for a given type, the `impl` form
27+
is used. This defines implementations of `to_str` for the `int` and
28+
`str` types.
29+
30+
# iface to_str { fn to_str() -> str; }
31+
impl of to_str for int {
32+
fn to_str() -> str { int::to_str(self, 10u) }
33+
}
34+
impl of to_str for str {
35+
fn to_str() -> str { self }
36+
}
37+
38+
Given these, we may call `1.to_str()` to get `"1"`, or
39+
`"foo".to_str()` to get `"foo"` again. This is basically a form of
40+
static overloading—when the Rust compiler sees the `to_str` method
41+
call, it looks for an implementation that matches the type with a
42+
method that matches the name, and simply calls that.
43+
44+
## Scoping
45+
46+
Implementations are not globally visible. Resolving a method to an
47+
implementation requires that implementation to be in scope. You can
48+
import and export implementations using the name of the interface they
49+
implement (multiple implementations with the same name can be in scope
50+
without problems). Or you can give them an explicit name if you
51+
prefer, using this syntax:
52+
53+
# iface to_str { fn to_str() -> str; }
54+
impl nil_to_str of to_str for () {
55+
fn to_str() -> str { "()" }
56+
}
57+
58+
## Bounded type parameters
59+
60+
The useful thing about value polymorphism is that it does not have to
61+
be static. If object-oriented languages only let you call a method on
62+
an object when they knew exactly which sub-type it had, that would not
63+
get you very far. To be able to call methods on types that aren't
64+
known at compile time, it is possible to specify 'bounds' for type
65+
parameters.
66+
67+
# iface to_str { fn to_str() -> str; }
68+
fn comma_sep<T: to_str>(elts: [T]) -> str {
69+
let result = "", first = true;
70+
for elt in elts {
71+
if first { first = false; }
72+
else { result += ", "; }
73+
result += elt.to_str();
74+
}
75+
ret result;
76+
}
77+
78+
The syntax for this is similar to the syntax for specifying that a
79+
parameter type has to be copyable (which is, in principle, another
80+
kind of bound). By declaring `T` as conforming to the `to_str`
81+
interface, it becomes possible to call methods from that interface on
82+
values of that type inside the function. It will also cause a
83+
compile-time error when anyone tries to call `comma_sep` on an array
84+
whose element type does not have a `to_str` implementation in scope.
85+
86+
## Polymorphic interfaces
87+
88+
Interfaces may contain type parameters. This defines an interface for
89+
generalized sequence types:
90+
91+
iface seq<T> {
92+
fn len() -> uint;
93+
fn iter(block(T));
94+
}
95+
impl <T> of seq<T> for [T] {
96+
fn len() -> uint { vec::len(self) }
97+
fn iter(b: block(T)) {
98+
for elt in self { b(elt); }
99+
}
100+
}
101+
102+
Note that the implementation has to explicitly declare the its
103+
parameter `T` before using it to specify its interface type. This is
104+
needed because it could also, for example, specify an implementation
105+
of `seq<int>`—the `of` clause *refers* to a type, rather than defining
106+
one.
107+
108+
## Casting to an interface type
109+
110+
The above allows us to define functions that polymorphically act on
111+
values of *an* unknown type that conforms to a given interface.
112+
However, consider this function:
113+
114+
# iface drawable { fn draw(); }
115+
fn draw_all<T: drawable>(shapes: [T]) {
116+
for shape in shapes { shape.draw(); }
117+
}
118+
119+
You can call that on an array of circles, or an array of squares
120+
(assuming those have suitable `drawable` interfaces defined), but not
121+
on an array containing both circles and squares.
122+
123+
When this is needed, an interface name can be used as a type, causing
124+
the function to be written simply like this:
125+
126+
# iface drawable { fn draw(); }
127+
fn draw_all(shapes: [drawable]) {
128+
for shape in shapes { shape.draw(); }
129+
}
130+
131+
There is no type parameter anymore (since there isn't a single type
132+
that we're calling the function on). Instead, the `drawable` type is
133+
used to refer to a type that is a reference-counted box containing a
134+
value for which a `drawable` implementation exists, combined with
135+
information on where to find the methods for this implementation. This
136+
is very similar to the 'vtables' used in most object-oriented
137+
languages.
138+
139+
To construct such a value, you use the `as` operator to cast a value
140+
to an interface type:
141+
142+
# type circle = int; type rectangle = int;
143+
# iface drawable { fn draw(); }
144+
# impl of drawable for int { fn draw() {} }
145+
# fn new_circle() -> int { 1 }
146+
# fn new_rectangle() -> int { 2 }
147+
# fn draw_all(shapes: [drawable]) {}
148+
let c: circle = new_circle();
149+
let r: rectangle = new_rectangle();
150+
draw_all([c as drawable, r as drawable]);
151+
152+
This will store the value into a box, along with information about the
153+
implementation (which is looked up in the scope of the cast). The
154+
`drawable` type simply refers to such boxes, and calling methods on it
155+
always works, no matter what implementations are in scope.
156+
157+
Note that the allocation of a box is somewhat more expensive than
158+
simply using a type parameter and passing in the value as-is, and much
159+
more expensive than statically resolved method calls.
160+
161+
## Interface-less implementations
162+
163+
If you only intend to use an implementation for static overloading,
164+
and there is no interface available that it conforms to, you are free
165+
to leave off the `of` clause.
166+
167+
# type currency = ();
168+
# fn mk_currency(x: int, s: str) {}
169+
impl int_util for int {
170+
fn times(b: block(int)) {
171+
let i = 0;
172+
while i < self { b(i); i += 1; }
173+
}
174+
fn dollars() -> currency {
175+
mk_currency(self, "USD")
176+
}
177+
}
178+
179+
This allows cutesy things like `send_payment(10.dollars())`. And the
180+
nice thing is that it's fully scoped, so the uneasy feeling that
181+
anybody with experience in object-oriented languages (with the
182+
possible exception of Rubyists) gets at the sight of such things is
183+
not justified. It's harmless!

trunk/doc/tutorial/order

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ data
77
args
88
generic
99
mod
10+
iface
1011
ffi
1112
task
1213
test

0 commit comments

Comments
 (0)