Interface Values
An interface is a type in its own right. A value of an interface type holds a value of any type that implements the interface, together with a note of which type that is, and a call through it runs that type's method. Choosing the method while the program runs, by the value inside, is dynamic dispatch.
Forms
| Written | What it is | Changes reach the original? |
|---|---|---|
let shape: Shape = wheel; | an interface value: a copy of wheel, owned | no |
let shape: Shape <- key; | an interface value: key moved in | — key is gone |
shape: &Shape | a read-only borrow of an implementing value | it cannot change anything |
shape: &var Shape | a writable borrow of an implementing value | yes |
shapes: Shape[2] | an array of interface values | no |
args: Shape... | a variadic parameter of interface values | no |
In each case the conversion from the concrete type happens where the value is bound, assigned or passed. No &, cast or call is written.
Holding a value
The annotation is what turns a concrete value into an interface value. Without it, var shape = wheel; is simply another Circle.
var shape: Shape = wheel;
PrintLine("{} {}", shape.Name(), shape.Area());
shape = block; // a Square now: the same calls run Square's code
PrintLine("{} {}", shape.Name(), shape.Area());
flowchart LR
call["shape.Area()"] --> note{"Which type is<br/>inside shape now?"}
note -- "a Circle" --> c["Circle's Area"]
note -- "a Square" --> s["Square's Area"]An interface value holds a copy, made by the copy rules of the concrete type, or the value itself when it is moved in with <-. Changing the original afterwards leaves the interface value alone, and a writing requirement called on a var interface value changes the copy inside, not the original.
An interface value owns what it holds, and should destroy it when its own life ends. rux 0.4.0 does not yet do so: the destructor of a value stored in an interface value never runs.
Many types in one array
An array has one element type, so values of different types share one only as interface values. The annotation gives every element the interface type:
let shapes: Shape[2] = [wheel, block];
var total = 0.0;
for each in shapes {
total += each.Area();
}
Without it, the first element decides the element type, and the second is refused: array element 2 has type 'Square', but element 1 established element type 'Circle'.
Borrowing through an interface
A parameter of type &I or &var I borrows the caller's own value, whatever its type, as long as it implements I. Nothing is copied, and the borrow follows the usual reference rules:
interface Gauge {
func Read() -> int32;
func Adjust(self: &var Self, amount: int32);
}
struct Dial {
level: int32;
}
extend Dial : Gauge {
func Read(self: &Dial) -> int32 {
return self.level;
}
func Adjust(self: &var Dial, amount: int32) {
self.level += amount;
}
}
func Show(gauge: &Gauge) {
PrintLine("reads {}", gauge.Read());
}
func TurnUp(gauge: &var Gauge, amount: int32) {
gauge.Adjust(amount);
}
func Main() -> int {
var dial = Dial { level: 4 };
Show(dial);
TurnUp(dial, 5);
PrintLine("dial.level is {}", dial.level); // 9
return 0;
}
A requirement declared with self: &var Self writes, so it is callable only through a writable view:
error: cannot call 'Adjust' through immutable reference '&Gauge'
note: 'Adjust' declares a writable receiver '&var Self'
help: declare the reference as '&var Gauge'
A writable borrow needs writable storage: passing a let binding to a &var Gauge parameter fails with argument 1 to 'TurnUp' cannot borrow immutable 'fixed' as '&var Gauge'. A writable concrete borrow, &var Dial, converts to a read-only &Gauge as well.
Only the interface is visible
Through an interface value or borrow, only the interface's requirements can be reached. The value inside might be of any implementing type the next time the line runs, so fields and the type's other methods are out of reach:
error: interface type 'Gauge' has no member 'level'
Interface values and bounds
An interface value and a bound both use an interface, in different ways:
Interface value: shape: Shape | Bound: <T: Shape> | |
|---|---|---|
| Which type | any implementing type, decided while running | one concrete type per instantiation |
| A method call | looked up through the value at run time | direct, as if written for that type |
| What comes back | the interface; the concrete type is hidden | the same T that went in |
| Mixed types at once | yes — one array, one parameter list | no — every T is the same type |
| Conformance | declared with extend T : Shape | any type with the required methods |
See also
- Interfaces — declaring and implementing an interface
- References — the borrow rules
&Iand&var Ifollow - Bounds — the statically dispatched alternative
- Learn: Interface value, Interface parameter
Overview
An interface names a set of method requirements. A type implements it in an extend block, and is then usable as an interface value or a bound.
Core Interfaces
The interfaces the standard packages build on: Equatable, Comparable and Ordering, Hashable, Iterator and Iterable in Core; Display and Debug in Text.