Skip to content

Interfaces, abstract classes, and inheritance

A class has one base class and may implement many interfaces. Interfaces may extend other interfaces.

interface Shape {
fn area() -> f64
// an interface method may carry a default body
fn describe() -> string {
return "shape with area {self.area()}"
}
}
interface NamedShape extends Shape {
fn name() -> string
}
abstract class Drawable {
abstract fn draw()
fn visible() -> bool {
return true
}
}
class Circle extends Drawable implements NamedShape {
r: f64
fn init(r: f64) {
self.r = r
}
override fn draw() {}
// First body for an interface requirement: no override.
fn area() -> f64 {
return 3.14159265 * self.r * self.r
}
fn name() -> string {
return "circle"
}
// Replacing an interface default body needs override.
override fn describe() -> string {
return "{self.name()} with area {self.area()}"
}
}
  • extends takes one class base; implements takes comma-separated interfaces.
  • Interface requirements and default methods are instance methods. Static interface methods are not supported.
  • An abstract class may mix bodyless abstract fn declarations with normal methods. It cannot be constructed with new.
  • A concrete subclass must implement every inherited abstract method and every bodyless interface requirement.
  • A pub interface exposes its whole method set to other packages.
  • Interfaces cannot declare priv methods. Private class methods do not implement interface requirements or replace inherited methods.
  • Beans has no final yet.

override means “replace a method that already has a slot in a base contract.” The rule depends on where the method came from:

Inherited methodWrite override?
concrete base-class methodyes
abstract base-class methodyes
interface method with a default bodyyes
bodyless interface requirement, first implementationoptional

Using override when no base method or interface requirement matches is an error. Leaving it out when it is required is also an error. This catches method-name typos in both paths.

priv means a fresh method owned by one exact class or struct, not an override slot. For that reason priv abstract fn and priv override fn are errors.

An abstract declaration has no body and may appear only inside an abstract class:

abstract class Job {
abstract fn run() -> int
}
class BuildJob extends Job {
override fn run() -> int {
return 1
}
}

super.init(...) chains construction. The order is fixed: own fields first, then the parent’s constructor, then the fully-built object:

class Dog extends Animal {
breed: string
fn init(breed: string, name: string) {
self.breed = breed // 1. this class's own fields
super.init(name) // 2. the parent's constructor, exactly once
self.bark() // 3. everything is assigned, anything goes
}
}
  • Before super.init, the parent’s fields do not exist yet (not even defaulted ones). Assigning one is an error; super.init owns them.
  • super.init runs exactly once, as a top-level statement, only inside init, and is mandatory whenever a class above declares an init. A return before it is an error.
  • super.method(...) calls the nearest parent implementation directly, skipping virtual dispatch. It is valid only in an instance method.

A subclass whose added fields all have defaults inherits the nearest ancestor initializer. A subclass that adds a required field must declare its own init.

as? checks a reference’s real type and returns an Option. It never crashes:

let s: Shape = pick_a_shape()
match s as? Circle {
some(c) => io.println("circle, r = {c.r}"),
none => io.println("something else"),
}

Plain as is for explicit numeric casts and upcasts only.

A class implementing an interface, inheriting a default method:

import std.io
interface Greeter {
fn who() -> string
fn greet() -> string {
return "hi from {self.who()}"
}
}
class Robot implements Greeter {
id: int
fn init(id: int) {
self.id = id
}
fn who() -> string {
return "robot {self.id}"
}
}
fn main() {
let g: Greeter = new Robot(7)
io.println(g.greet())
}