v0.4.0

Interfaces

An interface names a set of functions that a type must provide. It has no fields and no code of its own: it lists function headers, and each type that implements it supplies the bodies in an extend block. Once a type implements an interface, its values can be borrowed as views and addressed by handles, the interface values with dynamic dispatch, and generic code can require the interface as a bound.

Declaring an interface

interface Name {
    func Requirement(parameters) -> Type;
    func Requirement(&self, parameters) -> Type;
    func Requirement(&var self, parameters) -> Type;
    func Requirement(parameters) -> Type throws Error;
}
interface Shape {
    func Area() -> float64;
    func Name() -> char8[..];
}

Each requirement is a function header ending in ;. An interface may declare any number of requirements, including none, and is private to its module unless declared pub interface.

A requirement says how it reaches the implementing value:

Requirement writtenThe implementation receivesCallable through
func Area() -> float64; (no receiver)&self — it reads&I, &var I, I
func Peek(&self) -> int32;&self — it reads&I, &var I, I
func Turn(&var self, by: int32);&var self — it may write&var I, a var I

The receiver names no type: inside an interface, &self borrows the implementing value for reading and &var self for writing.

A receiver taken by value is refused, because an interface reaches the implementing value through a reference:

error: receiver of requirement 'Consume' in interface 'Sink' must be '&Self' or '&var Self'
  note: a requirement reaches the implementing value through the data half of an interface view
  help: write '&var self' if 'Consume' writes through its receiver, or '&self' if it only reads it

Self

Inside an interface, Self names the type that implements it. It is how an interface says that an operand or a result has the implementer's own type — Core::Equatable declares func Equals(other: &Self) -> bool;, so a Fraction is compared only with another Fraction. Inside an extend block Self names the extended type; anywhere else it is 'Self' is only available inside an 'extend' block or an interface.

Generic interfaces

An interface may take type parameters to name the types its operations work with, as Core's interface Cursor<T> { func Next(&var self) -> T?; } does. A generic interface is only a bound: func Map<I: Cursor<T>, T, U>(source: I, transform: func(T) -> U) may call source.Next() and gets a T?, while using Cursor<int32> as the type of a value, a field or a parameter is an error:

error: generic interface 'Cursor' can only be used as a type-parameter bound

A bound names exactly as many type arguments as the interface declares, and an extend Counter : Cursor<int32> names them too; an interface's own parameters take no bounds. A type satisfies such a bound when its methods have the names the interface requires and return the types the bound's arguments fix, whether or not it says so in an extend. Core::Iterator itself lists no requirements, because an iterator's item type differs per implementation; it only names the protocol.

No bodies

A requirement has no body and an interface supplies no default implementations: every implementing type writes every requirement. A requirement written with a body, such as func Area() -> float64 { return 1.0; }, is refused:

error: requirement 'Area' of interface 'Shape' cannot have a body
  note: an interface declares what its implementations provide; it supplies no default
  help: end the requirement with ';', and write the body in each 'extend Type : Shape'

A requirement cannot be unsafe, and neither can a method that implements one, because a call through the interface does not show which implementation it reaches.

Implementing an interface

extend Type : Interface { methods }
extend Interface for Type { methods }

The two spellings are equivalent. Inside the block, every requirement appears as an ordinary method, with the receiver of the concrete type in front of the requirement's own parameters:

struct Circle {
    radius: float64;
}

extend Circle : Shape {
    func Area(&self) -> float64 {
        return 3.14 * self.radius * self.radius;
    }

    func Name(&self) -> char8[..] {
        return "circle";
    }
}

struct Square {
    side: float64;
}

extend Shape for Square {
    func Area(&self) -> float64 {
        return self.side * self.side;
    }

    func Name(&self) -> char8[..] {
        return "square";
    }
}

The rules:

  • Every requirement must be present. A missing one is reported on the extend line: implementation of interface 'Shape' for type 'Circle' is missing method 'Name'.
  • The parameters, the result and the error must match the requirement exactly, with Self standing for the implementing type. Only the receiver is added. A method func Area(&self) -> int32 for the requirement func Area() -> float64; fails with method 'Area' does not match the requirement of interface 'Shape', and notes the two headers side by side. A requirement that declares throws E is implemented by a method that throws exactly E, and a type whose methods have the right names but the wrong signatures does not satisfy a bound either.
  • The receiver must not write more than the requirement allows. An implementation of a reading requirement that takes &var self fails with method 'Adjust' of 'Dial' writes through its receiver, but requirement 'Adjust' of interface 'Gauge' only reads it. The opposite is allowed: an implementation of a &var self requirement may take &self if it does not need to write.
  • The receiver is a reference. A receiver taken by value, self, is refused in a block that implements an interface.
  • One interface per block. extend Circle : Shape, Named does not parse; a type that implements several interfaces has one extend block for each.
  • The block may add methods that the interface does not require. They belong to the type, not to the interface.

Methods that implement an interface are ordinary methods of the type, and are called on it like any other: wheel.Area().

Which types can implement

Any type with a declaring package can implement an interface: a structure, an enum, a variant, a primitive such as int32, a slice type such as char8[..], and a generic type through extend Box<T> : Shape (see Generic types). A native form — an optional, a sum or the unit type — has no declaring package to own the implementation:

error: cannot extend native type 'int32?'
  note: a sum, optional, or unit type has no declaring package to own methods or interface implementations
  help: write a generic function that takes the native type as a parameter

An interface from another package is imported before it is implemented: import Core::Equatable;, then extend Fraction : Equatable { … }.

Declared, not inferred

Implementing an interface is a declaration. A plain extend Circle { … } that happens to contain the right methods does not make Circle a Shape: let shape: &Shape = wheel; then fails with cannot assign 'Circle' to '&Shape'. Generic bounds are the exception — they are satisfied by any type that has the required methods.

Using an interface

WrittenWhat it isPage
func Show(shape: &Shape)a borrow of any implementing valueInterface values
shapes: Shape[2]handles to values stored elsewhereInterface values
func Best<T: Shape>(a: T, b: T)a generic accepting implementing typesBounds

The standard packages define a handful of interfaces the language and libraries build on — equality, ordering, hashing, text output and iteration; see Core interfaces. Operators, indexing and for loops are not interfaces: a type provides them by declaring operator functions, indexers and the iteration methods.

See also