Interfaces

Interfaces

Interfaces define contracts that classes can implement.

Basic Interface

interface Printable {
  function print() void;
}
 
interface HasValue {
  var value: i32;
}
 
class Counter implements Printable, HasValue {
  function init(v: i32) {
    this.value = v;
  }
 
  function print() void {
    println(this.value);
  }
}

Visibility

Interfaces and their members are private by default and are marked public individually. A private interface member is only callable through the interface from inside the interface's module; a class implementing a public member must declare its implementation public. Interface fields inherited by a class keep the visibility they were declared with on the interface.

public module shapes {
    public interface IShape {
        public function area() f64;
        function debug_id() i32 { return 0; }   // module-internal default
    }
    public class Square implements IShape {
        var side: f64;
        public function init(s: f64) { this.side = s; }
        public function area() f64 { return this.side * this.side; }
    }
}

Const Methods

An interface member declared const function promises not to change the object, so it can be called through a const ref to the interface. Every implementation must then be a const function too (see Const Methods). A class may declare extra const methods the interface does not require.

interface IShape {
    const function area() f64;
}
function measure(s: const ref IShape) f64 { return s.area(); }

Interface Fields

Interfaces can declare fields that implementing classes must have:

interface Named {
    var name: i32;  // Implementing class must have this field
}
 
class Person implements Named {
    // 'name' field is inherited from Named interface
    
    function init(n: i32) {
        this.name = n;
    }
}

Default Implementations

Interfaces can provide default method implementations:

interface Answerable {
  function answer() i32 {
    return 42;
  }
}
 
class Thinker implements Answerable {
  function init() {}
}
 
function main() i32 {
    var t = Thinker();
    return t.answer();  // Uses default: returns 42
}

Classes can override default implementations:

class DeepThinker implements Answerable {
    function init() {}
 
    function answer() i32 {
        return 43;  // Override default
    }
}

Generic Interfaces

Interfaces can have type parameters, allowing generic contracts:

interface Container<T> {
    function get() T;
    function set(value: T) void;
}
 
class Box<T> implements Container<T> {
    var value: T;
 
    function init(v: T) {
        this.value = v;
    }
 
    function get() T {
        return this.value;
    }
 
    function set(v: T) void {
        this.value = v;
    }
}

Implementing Generic Interfaces with Concrete Types

A non-generic class can implement a generic interface with a specific type:

interface Wrapper<T> {
    function unwrap() T;
}
 
class IntWrapper implements Wrapper<i32> {
    var value: i32;
 
    function init(v: i32) {
        this.value = v;
    }
 
    function unwrap() i32 {
        return this.value;
    }
}

Generic Class Implementing Generic Interface

When a generic class implements a generic interface, the type parameters flow through:

interface Mappable<T, U> {
    function map(f: fn(T) U) U;
}
 
class Value<T> implements Mappable<T, T> {
    var data: T;
 
    function init(d: T) {
        this.data = d;
    }
 
    function map(f: fn(T) T) T {
        return f(this.data);
    }
}

Multiple Interfaces

Classes can implement multiple interfaces:

interface Runnable {
    function run() void;
}
 
interface Stoppable {
    function stop() void;
}
 
class Service implements Runnable, Stoppable {
    var running: bool;
 
    function init() {
        this.running = false;
    }
 
    function run() void {
        this.running = true;
    }
 
    function stop() void {
        this.running = false;
    }
}

Builtin and Stdlib Interfaces

IError (error handling, code() and message()) is builtin and cannot be redefined; see Builtin Types. The iteration protocol comes from the standard library (iterator.sun, see Iteration):

  • IIterator<T, Container> - next(c: ref Container) Option<T>; None ends the sequence
  • IIterable<T, Self> - iter() returning an iterator
using sun;
 
// Implementing IIterator (self-iterating pattern)
class NumberIterator implements IIterator<i32, NumberIterator> {
    var current: i32;
    var max: i32;
 
    function init(start: i32, end: i32) {
        this.current = start;
        this.max = end;
    }
 
    function next(self: ref NumberIterator) Option<i32> {
        if (this.current >= this.max) {
            return Option.None;
        }
        var result = this.current;
        this.current = this.current + 1;
        return Option.Some(result);
    }
}

Classes can implement multiple interfaces by separating them with commas.

⚠️

Attempting to redefine the builtin IError interface will result in a compilation error.