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
    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.

The held value is not destroyed yet.
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: 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