Skip to content

Classes & Polymorphism

While structs and enums form the core of data modeling in Wysna, the language also supports classes and interfaces when reference identity, encapsulation, and dynamic polymorphic dispatch are required.

Interfaces declare method contracts without providing implementations:

interface Shape {
fn area() -> float
fn perimeter() -> float
}

Classes define state and methods with explicit access control (public or private). Constructors are declared with fn init(...) and must return unit (or bare return). Receiver state is accessed via this:

interface Shape {
fn area() -> float
fn perimeter() -> float
}
class Rectangle implements Shape {
public width: float
public height: float
fn init(width: float, height: float) {
this.width = width
this.height = height
}
public fn area() -> float {
return this.width * this.height
}
public fn perimeter() -> float {
return 2.0 * (this.width + this.height)
}
}
class Circle implements Shape {
public radius: float
fn init(radius: float) {
this.radius = radius
}
public fn area() -> float {
return 3.14159265359 * this.radius * this.radius
}
public fn perimeter() -> float {
return 2.0 * 3.14159265359 * this.radius
}
}

Classes are instantiated by calling their type constructor ClassName(...) (or optionally with the new keyword new ClassName(...)):

interface Shape {
fn area() -> float
fn perimeter() -> float
}
class Rectangle implements Shape {
public width: float
public height: float
fn init(width: float, height: float) {
this.width = width
this.height = height
}
public fn area() -> float {
return this.width * this.height
}
public fn perimeter() -> float {
return 2.0 * (this.width + this.height)
}
}
class Circle implements Shape {
public radius: float
fn init(radius: float) {
this.radius = radius
}
public fn area() -> float {
return 3.14159265359 * this.radius * this.radius
}
public fn perimeter() -> float {
return 2.0 * 3.14159265359 * this.radius
}
}
fn print_shape_info(shape: Shape) -> unit {
print("Area: ", shape.area())
print("Perimeter:", shape.perimeter())
}
fn main() -> unit {
let rect: Shape = Rectangle(10.0, 5.0)
let circ: Shape = Circle(7.0)
print("Rectangle:")
print_shape_info(rect)
print("Circle:")
print_shape_info(circ)
}

Classes support single inheritance via extends. Subclasses can invoke the parent constructor using super.init(...) and call overridden methods with super.method(...):

class Polygon {
public name: str
fn init(name: str) {
this.name = name
}
public fn describe() -> str {
return "Polygon: " + this.name
}
}
class Triangle extends Polygon {
public base: float
public height: float
fn init(name: str, base: float, height: float) {
super.init(name)
this.base = base
this.height = height
}
public fn describe() -> str {
return super.describe() + " (Triangle)"
}
}

Unlike struct (which provides pure Copy-on-Write value semantics), instances of class have reference identity:

  • Assigning let b = a shares the underlying heap object.
  • Mutating a field through b.width = 20.0 is immediately visible through a.width.
  • To prevent cyclical reference memory leaks in graphs, use weak references via Weak[T], weak(obj) and upgrade(handle).

Under the hood, interface dispatch in Wysna utilizes compact, type-erased vtables generated in C99, guaranteeing constant time ($O(1)$) dynamic dispatch.