Interfaces
Interfaces define contracts that classes can implement.
Basic Interface
interface Printable {
method print() void;
}
interface HasValue {
var value: i32;
}
class Counter implements Printable, HasValue {
init(v: i32) {
this.value = v;
}
method print() void {
println(this.value);
}
}Extending an Interface
An interface can extend one parent. It inherits the parent's fields, required methods, and default implementations. Inheritance can span several levels, and the parent may be declared later in the same module.
/** Provides a readable value. */
interface Readable {
/** Returns the current value. */
method read() i32;
}
/** Adds a write operation to the readable contract. */
interface Writable extends Readable {
/** Replaces the current value. */
method write(value: i32) void;
}A class implementing Writable must provide both methods and also satisfies
Readable, including generic constraints requiring Readable. Parents may
be qualified or generic, such as interface Child<T> extends api.Parent<T>.
Lifetime arguments use the usual annotation syntax.
A child may replace an inherited method with the same signature, visibility, const/unsafe qualifiers, generic constraints, and lifetime contract. A body provides a new default; a declaration without a body requires implementing classes to supply the method. The nearest default wins, and a class method takes precedence over every default. Calls through a parent use the same selected implementation. Inherited field names cannot be redeclared.
Interfaces do not own values. Use ref Interface or const ref Interface to
borrow an existing concrete object. Creating a view or converting a child view
to a parent view never allocates on the heap or copies the object. The concrete
owner remains responsible for destruction, and a view cannot outlive it.
A borrowed interface allows method calls but cannot replace the concrete object
through assignment. Conversion from parent to child is not supported.
Bare interface types cannot be used for variables, fields, parameters, ordinary returns, or owned container elements. Store a concrete type, use a generic type parameter constrained by the interface, or store a borrowed view with a suitable lifetime.
Inheritance preserves member visibility: private members remain accessible only within their defining module. Multiple parents and inheritance cycles are rejected. A class may still implement several unrelated interfaces, but conflicting defaults require an explicit class implementation.
Borrowed views share one static dispatch table per concrete class and interface. The table contains method pointers and a parent table pointer when the interface extends another interface. Views have no destruction slot.
Rebuild existing .moon libraries: owning interface values have been removed,
and the interface dispatch-table layout has changed.
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 method area() f64;
method debug_id() i32 { return 0; } // module-internal default
}
public class Square implements IShape {
var side: f64;
init(s: f64) { this.side = s; }
public method area() f64 { return this.side * this.side; }
}
}Const Methods
An interface member declared const method 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 method too (see
Const Methods). A class may declare extra const
methods the interface does not require.
interface IShape {
const method area() f64;
}
function measure(s: const ref IShape) f64 { return s.area(); }Methods That Return Errors
An interface method declares its result enum as its return type, including methods with default implementations:
/** Describes an operation that may fail. */
interface Operation {
/** Runs the operation or returns an owned system error. */
method run() SystemResult<void>;
}
/** Propagates an error from interface dispatch. */
function invoke(operation: ref Operation) SystemResult<void> {
try operation.run();
return SystemResult.Ok;
}Implementations and inherited overrides must preserve the method's result
type. An implementation that always succeeds still explicitly returns the
success variant. Callers may match the result, propagate it with try, or
explicitly discard it with ignore_error. See Error Handling.
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
init(n: i32) {
this.name = n;
}
}A class can own a concrete implementation through a generic parameter:
/** Handles an input value. */
interface Handler {
/** Transforms the input. */
method handle(value: i32) i32;
}
/** Doubles each input. */
class Doubler implements Handler {
/** Creates the handler. */
init() {}
/** Doubles the input. */
method handle(value: i32) i32 { return value * 2; }
}
/** Owns its concrete handler directly. */
class Service<H: Handler> {
var handler: H;
/** Moves the concrete handler into the service. */
init(handler: H) { this.handler = handler; }
/** Handles one input. */
method run(value: i32) i32 { return this.handler.handle(value); }
}Service<Doubler>(Doubler()) stores the handler directly and destroys it when
the service is dropped. Code needing dynamic dispatch can borrow the concrete
handler as ref Handler.
Interface Return Requirements
A bodyless interface method may name an interface as its return requirement.
Each implementation must return a concrete class implementing that interface.
This is a compile-time contract used by generic code, including
IIterable.iter(). Such an implementation cannot be converted to a borrowed
view of that interface for dynamic dispatch. An ordinary function or a default
method body cannot return an owning interface value.
Default Implementations
Interfaces can provide default method implementations:
interface Answerable {
method answer() i32 {
return 42;
}
}
class Thinker implements Answerable {
init() {}
}
function main() i32 {
var t = Thinker();
return t.answer(); // Uses default: returns 42
}Classes can override default implementations:
class DeepThinker implements Answerable {
init() {}
method answer() i32 {
return 43; // Override default
}
}Generic Interfaces
Interfaces can have type parameters, allowing generic contracts:
interface Container<T> {
method get() T;
method set(value: T) void;
}
class Box<T> implements Container<T> {
var value: T;
init(v: T) {
this.value = v;
}
method get() T {
return this.value;
}
method 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> {
method unwrap() T;
}
class IntWrapper implements Wrapper<i32> {
var value: i32;
init(v: i32) {
this.value = v;
}
method 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> {
method map(f: fn(T) U) U;
}
class Value<T> implements Mappable<T, T> {
var data: T;
init(d: T) {
this.data = d;
}
method map(f: (T) => T) T {
return f(this.data);
}
}Lifetimes in Interface Methods
An interface method may tie a parameter to the receiver with the builtin
'this lifetime, exactly as a class method does — and the tie is part of
the contract. An implementing class must repeat the interface's lifetime
names verbatim; dropping or renaming one is a conformance error, since a
caller dispatching through the interface sees only the interface's
signature:
interface ISink {
public method accept(cb: <'this>(i32) => i32) void;
}
class Holder implements ISink {
var cb: <'_>(i32) => i32;
/* must say <'this> here too - a plain <'_> is rejected */
public method accept(cb: <'this>(i32) => i32) void { this.cb = cb; }
}Call sites are then checked against the implementing object's real scope, the same as for classes (see Memory Safety).
Multiple Interfaces
Classes can implement multiple interfaces:
interface Runnable {
method run() void;
}
interface Stoppable {
method stop() void;
}
class Service implements Runnable, Stoppable {
var running: bool;
init() {
this.running = false;
}
method run() void {
this.running = true;
}
method stop() void {
this.running = false;
}
}Builtin and Stdlib Interfaces
IError (error handling, code() and message()) is builtin and cannot be
redefined; see Builtin Types. catch (error: ref IError) borrows
a runtime-owned exception. Catch bindings must explicitly use ref or
const ref; they never own the error. Exception transport still allocates and is
separate from allocation-free interface views.
The iteration protocol comes
from the standard library (iterator.sun, see Iteration):
IIterator<T, Container>-next(c: ref Container) Option<T>;Noneends the sequenceIIterable<T, Self>-iter()returning an iterator
using std;
// Implementing IIterator (self-iterating pattern)
class NumberIterator implements IIterator<i32, NumberIterator> {
var current: i32;
var max: i32;
init(start: i32, end: i32) {
this.current = start;
this.max = end;
}
method 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.