Class
class is a composite type with reference semantics: assignment and argument passing share the same object, so changing an alias changes the original. Classes support single inheritance, virtual methods, abstract and static members, and are the primary declaration form for data that carries behavior and identity.
Declaration and Construction
class Animal
{
public var name: string = "?";
init(n: string)
{
name = n;
}
public virtual func Speak() -> string => "...";
}
class Dog : Animal
{
public var tricks: int = 0;
init(n: string) : base(n)
{
}
public override func Speak() -> string => "woof";
}Constructors are written init, triggered through new, and cannot be called directly. : base(n) chains to the base-class constructor; omitting the chain is equivalent to : base(). virtual declares a method that may be overridden, and override supplies a new implementation in a derived class; dispatch follows the runtime type.
Reference Semantics
val d = new Dog("Rex");
val alias = d;
alias.tricks = 5;
print(d.tricks);5alias and d are the same object. This contrasts with the struct’s value copy: when you need an independent copy, the class must provide its own Clone-style method (see the Self example in Protocol).
Constructor Delegation and base
Constructors delegate to same-class overloads with : init(...) and chain to the base class with : base(...); omitting the chain is equivalent to : base():
class Widget
{
public val serial: int;
public var label: string = "standard";
init(serial: int, label: string)
{
self.serial = serial;
self.label = label;
}
init(serial: int) : init(serial, "standard")
{
}
}
class Gadget : Widget
{
public var watts: int = 0;
init(s: int) : base(s, "appliance")
{
watts = 5;
}
}
val w = new Widget(1);
print(w.serial, w.label);
val g = new Gadget(2);
print(g.serial, g.label, g.watts);1 standard
2 appliance 5new Widget(1) walks the delegation chain to fill in the default label; Gadget’s init sets the label to “appliance” through base.
Primary Constructors and the Overload Set
The primary constructor (class Name(args) { }) promotes its parameters to private val fields and joins the constructor overload set: if the argument count matches an explicit init, that init runs; otherwise, if the arguments match the primary constructor’s parameter shape, the primary-constructor path runs. When an explicit init and the primary constructor share the same parameter shape, the explicit init wins:
class Pack(n: int)
{
public var shown: string = "unset";
init(n: int)
{
shown = "explicit";
}
}
class Crate(n: int)
{
public var shown: string = "primary";
init()
{
shown = "zero-arg";
}
}
print(new Pack(1).shown);
print(new Crate(7).shown);
print(new Crate().shown);explicit
primary
zero-argThe primary constructor has no chaining syntax for now; omitting the chain follows the “implicit : base()” rule.
Types with an explicit constructor also carry a definite-assignment contract: a val instance field without an initializer must be assigned within the union of assignments across every constructor (including : init(...) delegation chains), or the compiler reports an error. var fields are exempt thanks to their zero value, and so are types without an explicit constructor (they go through the object-initializer path).
Fallible Construction
When an init carries a fail clause, the new expression itself is fallible and the call site must consume it; with try? catching the failure, it produces null:
error BadCode(code: int);
class Vault
{
public val code: int;
init(code: int) fail BadCode
{
if code < 0
{
fail new BadCode(code);
}
self.code = code;
}
}
val v = try? new Vault(9);
print(v?.code);
val bad = try? new Vault(-1);
print(bad == null);9
TrueThe failure escapes level by level along the construction and delegation chain. Calling fallible constructors inside field initializers is forbidden.
Polymorphism
A base-class reference holds a derived object, and virtual methods dispatch by the actual type:
val zoo = new list<Animal>();
zoo.Add(new Animal("_GENERIC_"));
zoo.Add(new Dog("Rex"));
for pet in zoo
{
print(pet.Speak());
}...
woofShadowing vs. Overriding
When the base-class method is not marked virtual, a same-named method in the derived class shadows rather than overrides:
class Plain
{
public func Go1() -> string => "base";
public virtual func Go2() -> string => "base";
}
class Shadowed : Plain
{
public func Go1() -> string => "derived";
public override func Go2() -> string => "derived";
}
val d: Plain = new Shadowed();
print(d.Go1());
print(d.Go2());base
derivedFor polymorphism, mark the base method virtual and the derived one override. If you truly want shadowing (an unrelated method that happens to share the name), rename as the warning suggests, or acknowledge and ignore it.
Abstract and Sealed
abstract class Shape
{
public abstract func Area() -> float;
}
class Square : Shape
{
public var side: float = 1.0;
public override func Area() -> float => side * side;
}An abstract class cannot be instantiated; abstract members have no method body, and derived classes must override them. A sealed class forbids further inheritance.
Static Members
class Counter
{
public static var total: int = 0;
public var id: int = 0;
init()
{
Counter.total += 1;
id = Counter.total;
}
}
val c1 = new Counter();
val c2 = new Counter();
print(c1.id, c2.id, Counter.total);1 2 2Static members hang off the type and are accessed as TypeName.member. A static class is the dedicated form for static utility classes: members are forced static, the class is implicitly sealed and cannot be inherited. Initializers of static fields join global initialization.
Custom Indexers
operator [] declares index reads and operator []= declares index writes; the index can take any number of arguments (the matrix m[i, j] case):
class Box3
{
val cells: list<int> = new list<int>();
init()
{
for i in 0..3
{
cells.Add(0);
}
}
public operator [](self, i: int) -> int => cells[i];
public operator []=(self, i: int, v: int)
{
cells[i] = v;
}
}
func main() -> int
{
val b = new Box3();
b[1] = 9;
b[1] += 5;
print(b[1], b[0]);
return 0;
}14 0- Compound assignments like
b[i] += 5desugar into a read plus a write; both go through the declared indexer. - Multi-argument indexing works the same way:
operator [](self, x: int, y: int) -> intpairs withg[1, 2]. - Indexer parameters do not support
ref/out.
Notes
- The bodyless semicolon form
class Marker;is equivalent to an empty class body. - Marking
init/deinitasvirtual,override, orabstractraises MS4025: constructors and destructors take no part in virtual dispatch. - In the scripting edition of MushScript, the destructor
deinit()fires only when a using block exits; ordinary block exits make no promise. - The primary constructor promotes its parameters to private val fields and joins the overload set alongside explicit inits; see “Primary Constructors and the Overload Set” above.
- A class implements a protocol with
impl, a clause separate from the inheritance:; see Protocol.
