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

WrittenWhat it isChanges reach the original?
let shape: Shape = wheel;an interface value: a copy of wheel, ownedno
let shape: Shape <- key;an interface value: key moved in— key is gone
shape: &Shapea read-only borrow of an implementing valueit cannot change anything
shape: &var Shapea writable borrow of an implementing valueyes
shapes: Shape[2]an array of interface valuesno
args: Shape...a variadic parameter of interface valuesno

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
    invoke["`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. The held value is destroyed exactly once, when the scope the interface value was made in ends, as a local of that scope would be — a value moved in with <- included, and the moved-from variable is not destroyed again.

A value stored in a variable's interface value, by its initializer or a later assignment to the variable or to a field of it, belongs to the variable instead: it lives as long as the variable, even when a loop body or a branch stored it, and storing another value of the same type in the same place destroys the old one at the assignment:

var shape: Named = Tracked { id: 1 };
shape = Tracked { id: 2 };      // Tracked 1 is destroyed here
var i = 3;
while i <= 5 {
    shape = Tracked { id: i };  // each assignment destroys the value before it
    i += 1;
}
// shape holds Tracked 5, destroyed when shape's scope ends

A value of another type that the place held before stays alive until the variable's scope ends, since a copy of the interface value may still point at it. An element of an array of interface values has no place of its own, so storing one made in a block inside the array's own, such as a loop body, is refused:

error: an interface value made in this block cannot be stored in an element of 'items', which outlives the block

The held value lives in the storage of the function that made the interface value, so a function cannot make one at its return from a concrete value. Return the concrete type, and let the caller make the interface value:

error: cannot return 'Tracked' as interface value 'Named'
  help: return 'Tracked' itself, and let the caller make the interface value

The same holds for a local that holds an interface value the function made, whether by its initializer, by an assignment or by copying another such local; an interface value the function was given is returned as it is, through a local too:

error: cannot return interface value 'value', which holds a value made in 'Make'
  note: 'value' points at storage 'Make' gives up when it returns
  help: return the concrete type, and let the caller make the interface value

Nor can it be stored where it outlives the call — through self, a pointer or a reference, or in a module variable — and an optional interface value counts the same:

error: cannot store an interface value made in 'Keep' where it outlives the call
  note: it points at storage 'Keep' gives up when it returns
  help: store the concrete value, and make the interface value where it is used

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: ShapeBound: <T: Shape>
Which typeany implementing type, decided while runningone concrete type per instantiation
A method calllooked up through the value at run timedirect, as if written for that type
What comes backthe interface; the concrete type is hiddenthe same T that went in
Mixed types at onceyes — one array, one parameter listno — every T is the same type
Conformancedeclared with extend T : Shapeany type with the required methods

See also