5.1.43. Interfaces
This tutorial covers interface-based polymorphism using the
daslib/interfaces module. Interfaces let you define abstract
method contracts that structs can implement, enabling polymorphic
dispatch without class inheritance.
Prerequisites: Dynamic Type Checking (Tutorial 39).
require daslib/interfaces
5.1.43.1. Defining an interface
Use [interface] on a class to declare it as an interface. An
interface may contain only function-typed fields — abstract methods
or default implementations:
[interface]
class IGreeter {
def abstract greet(name : string) : string
}
5.1.43.2. Implementing an interface
Use [implements(IFoo)] on a struct to generate the proxy class
and getter. Implement each method with the InterfaceName`method
naming convention:
[implements(IGreeter)]
class FriendlyGreeter {
def FriendlyGreeter() {
pass
}
def IGreeter`greet(name : string) : string {
return "Hey, {name}!"
}
}
5.1.43.3. is / as / ?as operators
The InterfaceAsIs variant macro enables three operators that
work with interface types:
var g = new FriendlyGreeter()
// is — compile-time check (zero runtime cost)
print("{g is IGreeter}\n") // true
// as — returns the interface proxy
var iface = g as IGreeter
iface->greet("Alice")
// ?as — null-safe: returns null when the pointer is null
var nothing : FriendlyGreeter?
var safe = nothing ?as IGreeter // null
is is resolved entirely at compile time — the result is baked
into the program as a boolean constant.
5.1.43.4. Multiple interfaces
A struct can implement any number of interfaces by listing multiple
[implements(...)] annotations:
[interface]
class IDrawable {
def abstract draw(x, y : int) : void
}
[interface]
class ISerializable {
def abstract serialize : string
}
[implements(IDrawable), implements(ISerializable)]
class Sprite {
name : string
def Sprite(n : string) { name = n }
def IDrawable`draw(x, y : int) {
print("Sprite \"{name}\" at ({x},{y})\n")
}
def ISerializable`serialize() : string {
return "sprite:{name}"
}
}
5.1.43.5. Polymorphic dispatch
Interfaces enable true polymorphic dispatch — pass interface proxies to functions that accept the interface type:
def draw_all(var objects : array<IDrawable?>) {
for (obj in objects) {
obj->draw(0, 0)
}
}
var drawables : array<IDrawable?>
drawables |> push(circle as IDrawable)
drawables |> push(sprite as IDrawable)
draw_all(drawables)
5.1.43.6. Interface inheritance
Interfaces can extend other interfaces using normal class inheritance
syntax. A struct that implements a derived interface automatically
supports is/as/?as for all ancestor interfaces:
[interface]
class IAnimal {
def abstract name : string
def abstract sound : string
}
[interface]
class IPet : IAnimal {
def abstract owner : string
}
[implements(IPet)]
class Dog {
dog_name : string
owner_name : string
def Dog(n, o : string) { dog_name = n; owner_name = o }
def IPet`name() : string { return dog_name }
def IPet`sound() : string { return "Woof" }
def IPet`owner() : string { return owner_name }
}
Now Dog supports both IPet and IAnimal:
var d = new Dog("Rex", "Alice")
print("{d is IPet}\n") // true
print("{d is IAnimal}\n") // true
var animal = d as IAnimal
animal->name() // "Rex"
animal->sound() // "Woof"
5.1.43.7. Default method implementations
Non-abstract methods in an interface class provide default implementations. The implementing struct only needs to override the abstract methods — defaults are inherited by the proxy:
[interface]
class ILogger {
def abstract log_name : string
// Default — optional to override
def format(message : string) : string {
return "[{self->log_name()}] {message}"
}
}
[implements(ILogger)]
class SimpleLogger {
def SimpleLogger() { pass }
def ILogger`log_name() : string { return "simple" }
// format() uses ILogger's default
}
A struct that overrides the default provides its own implementation:
[implements(ILogger)]
class FancyLogger {
def FancyLogger() { pass }
def ILogger`log_name() : string { return "fancy" }
def ILogger`format(message : string) : string {
return "*** [fancy] {message} ***"
}
}
5.1.43.8. Completeness checking
If an implementing struct is missing an abstract method, the compiler reports an error at compile time:
error[30111]: Foo does not implement IBar.method
Methods with default implementations are optional — the proxy
inherits the default from the interface class. Only abstract
methods (def abstract) are required.
5.1.43.9. Quick reference
Syntax |
Description |
|---|---|
|
Mark a class as an interface |
|
Generate proxy and getter for |
|
Declare an abstract method (required) |
|
Declare a default method (optional to override) |
|
Implement (or override) a method on a struct |
|
Compile-time check (true/false) |
|
Get the interface proxy |
|
Null-safe proxy access |
|
Interface inheritance |
See also
Dynamic Type Checking — is/as on
class hierarchies (Tutorial 39).
Variant Macros — how the
InterfaceAsIs macro works internally (Macro Tutorial 8).
Interfaces module — standard library reference.
Full source: tutorials/language/43_interfaces.das
Previous tutorial: Testing Tools (faker + fuzzer)
Next tutorial: Compiling and Running Programs at Runtime