Skip to content

Classes

A class defines a custom type with fields (data) and methods (behavior). This page covers defining and using classes; the rest of this section goes deeper: Encapsulation & Accessors for access control, Static Members for class-level state, and Inheritance for subclassing.

Defining a class

Declare fields with a type, then create instances with new:

class Rectangle {
public width: f64;
public height: f64;
}
let rect: Rectangle = new Rectangle();
rect.width = 10.0;
rect.height = 5.0;

Constructors

A constructor initializes an object’s fields. It runs automatically when you write new ClassName(...):

class Point {
public x: i32;
public y: i32;
constructor(x: i32, y: i32) {
this.x = x;
this.y = y;
}
}
function main(): i32 {
let p: Point = new Point(10, 20);
return p.x + p.y; // => 30
}

Methods

Methods are functions attached to a class that operate on the instance:

class Rectangle {
public width: f64;
public height: f64;
constructor(w: f64, h: f64) {
this.width = w;
this.height = h;
}
public area(): f64 {
return this.width * this.height;
}
public perimeter(): f64 {
return 2.0 * (this.width + this.height);
}
}
let rect: Rectangle = new Rectangle(10.0, 5.0);
let a: f64 = rect.area(); // 50.0
let p: f64 = rect.perimeter(); // 30.0

Methods can take rest parameters — prefix the final parameter with ...:

class Accumulator {
public base: i32;
constructor(base: i32) {
this.base = base;
}
public sum(...nums: i32[]): i32 {
let total: i32 = this.base;
for (let i: i32 = 0; i < nums.length; i++) {
total += nums[i];
}
return total;
}
}
let acc: Accumulator = new Accumulator(10);
let total: i32 = acc.sum(1, 2, 3); // 16

The this keyword

Inside a method, this refers to the current instance — use it to read and update the object’s own fields:

class Counter {
public value: i32;
constructor(start: i32) {
this.value = start;
}
public increment(): void {
this.value = this.value + 1;
}
public getValue(): i32 {
return this.value;
}
}
function main(): i32 {
let c: Counter = new Counter(41);
c.increment();
return c.getValue(); // => 42
}

Object references

Objects are passed by reference. A function that mutates its parameter changes the caller’s object:

class Box {
public value: i32;
constructor(v: i32) { this.value = v; }
}
function modifyBox(box: Box): void {
box.value = 999;
}
function main(): i32 {
let myBox: Box = new Box(10);
modifyBox(myBox);
return myBox.value; // => 999
}

Nested objects

A field can hold another object, letting you compose larger structures:

class Point {
public x: i32;
public y: i32;
constructor(x: i32, y: i32) {
this.x = x;
this.y = y;
}
}
class Line {
public start: Point;
public end: Point;
constructor(p1: Point, p2: Point) {
this.start = p1;
this.end = p2;
}
public length(): i32 {
let dx: i32 = this.end.x - this.start.x;
let dy: i32 = this.end.y - this.start.y;
return dx + dy; // Manhattan distance
}
}
function main(): i32 {
let line: Line = new Line(new Point(0, 0), new Point(10, 20));
return line.length(); // => 30
}

Null and null-safety

An object variable can be null, either explicitly or when left uninitialized:

class Node {
public value: i32;
public next: Node;
constructor(v: i32) {
this.value = v;
this.next = null; // no next node yet
}
}

Zeus checks for null on every property access. Reaching through a null reference throws a NullReferenceException:

let person: Person; // null by default
console.log(person.name); // throws NullReferenceException
function getName(person: Person): string {
try {
return person.name;
} catch (e: Error) {
return "Unknown";
}
}

Classes with arrays

A field can be an array. Initialize it in the constructor, then use the array methods:

class Team {
public scores: i32[];
constructor() {
this.scores = new i32[];
}
public addScore(score: i32): void {
this.scores.push(score);
}
public getScore(index: i32): i32 {
return this.scores.get(index);
}
}
function main(): i32 {
let team: Team = new Team();
team.addScore(10);
team.addScore(20);
team.addScore(30);
return team.getScore(1); // => 20
}

Anonymous classes

A class expression creates a type inline, without a top-level declaration — handy for one-off objects.

Assign it to a const and use that name as the type:

function main(): i32 {
const Counter = class {
public n: i32;
constructor(start: i32) { this.n = start; }
inc(): i32 {
this.n = this.n + 1;
return this.n;
}
}
let c = new Counter(9);
return c.inc(); // => 10
}

You can also give the expression an internal name — both the variable and the class name are then usable:

function main(): i32 {
const Vec = class Vector {
public x: i32;
public y: i32;
constructor(x: i32, y: i32) {
this.x = x;
this.y = y;
}
sum(): i32 { return this.x + this.y; }
}
let a = new Vec(3, 4); // via the const
let b = new Vector(1, 2); // via the class name
return a.sum() + b.sum(); // => 10
}

Or define and instantiate in a single expression — the class equivalent of an IIFE:

function main(): i32 {
let obj = new (class {
public v: i32;
constructor(v: i32) { this.v = v; }
get(): i32 { return this.v; }
})(42);
return obj.get(); // => 42
}