Skip to content

Classes

Classes are reference types with methods, inheritance, and optional virtual dispatch. Unlike structs (value types), classes are typically heap-allocated.

Defining Classes

nic
class Counter {
    pub value: i32;
    
    pub init(self) -> unit {
        self.value = 0;
        return;
    }
    
    pub increment(self) -> unit {
        self.value = self.value + 1;
        return;
    }
    
    pub get(self) -> i32 {
        return self.value;
    }
}

Methods take self as the first parameter to access instance fields.

Creating Instances

Use new to allocate class instances:

nic
fn main() -> unit {
    let c: *Counter = new Counter();
    
    c.increment();
    c.increment();
    let val: i32 = c.get();  // 2
    
    release c;  // must free heap memory
    return;
}

Auto-Dereferencing

Method calls and field access auto-dereference pointers:

nic
fn main() -> unit {
    let c: *Counter = new Counter();
    
    // No need for (*c).value or c->value
    c.value = 10;      // auto-deref for field access
    c.increment();     // auto-deref for method call
    
    release c;
    return;
}

Inheritance

Classes support single inheritance with ::

nic
class Animal {
    pub name: i32;
    pub age: i32;
    
    pub init(self) -> unit {
        self.age = 0;
        return;
    }
    
    pub speak(self) -> i32 {
        return 0;
    }
}

class Dog : Animal {
    pub breed: i32;
    
    pub speak(self) -> i32 {
        return 42;  // Woof!
    }
}

fn main() -> unit {
    let d: *Dog = new Dog();
    
    d.name = 10;     // inherited field
    d.breed = 3;     // own field
    
    release d;
    return;
}

Polymorphism

Derived class pointers can be assigned to base class pointers:

nic
fn main() -> unit {
    let d: *Dog = new Dog();
    d.name = 10;
    
    let a: *Animal = d;  // OK: *Dog is subtype of *Animal
    
    release d;
    return;
}

Abstract Classes

Abstract classes cannot be instantiated directly:

nic
abstract class Shape {
    pub area(self) -> f64 {
        return 0.0;
    }
}

class Circle : Shape {
    pub radius: f64;
    
    pub area(self) -> f64 {
        return 3.14159 * self.radius * self.radius;
    }
}

fn main() -> unit {
    // let s = new Shape();  // ERROR: cannot instantiate abstract class
    
    let c: *Circle = new Circle();
    c.radius = 5.0;
    
    let s: *Shape = c;  // OK: polymorphic assignment
    
    release c;
    return;
}

Virtual Methods

By default, methods use static dispatch. Use virtual for dynamic dispatch:

nic
abstract class Animal {
    pub name: i32;
    
    pub virtual speak(self) -> i32 {
        return 0;
    }
}

class Dog : Animal {
    pub virtual speak(self) -> i32 {
        return 1;  // Woof!
    }
}

class Cat : Animal {
    pub virtual speak(self) -> i32 {
        return 2;  // Meow!
    }
}

fn make_sound(animal: *Animal) -> i32 {
    return animal.speak();  // dynamic dispatch via vtable
}

fn main() -> i32 {
    let d: *Dog = new Dog();
    let c: *Cat = new Cat();
    
    let r1: i32 = make_sound(d);  // returns 1
    let r2: i32 = make_sound(c);  // returns 2
    
    release d;
    release c;
    return r1 + r2;
}

Constructors and Destructors

Use init and deinit for lifecycle management:

nic
class Resource {
    pub id: i32;
    pub active: bool;
    
    pub init(self) -> unit {
        self.active = true;
        return;
    }
    
    pub deinit(self) -> unit {
        self.active = false;
        return;
    }
    
    pub use(self) -> i32 {
        return self.id;
    }
}

fn main() -> i32 {
    let r: *Resource = new Resource();  // init called
    r.id = 42;
    
    let result: i32 = r.use();
    
    release r;  // deinit called, then memory freed
    return result;
}

Constructors can also take parameters:

nic
class Point {
    pub x: i32;
    pub y: i32;
    
    pub init(self, x_val: i32, y_val: i32) -> unit {
        self.x = x_val;
        self.y = y_val;
        return;
    }
}

fn main() -> i32 {
    let p: *Point = new Point(10, 20);  // calls init with args
    return p.x + p.y;  // 30
}

Visibility

Fields and methods are private by default:

nic
class Account {
    pub name: i32;         // public
    balance: f64;          // private
    
    pub deposit(self, amount: f64) -> unit {
        self.balance = self.balance + amount;
        return;
    }
    
    internal_check(self) -> bool {
        return self.balance >= 0.0;
    }
}

Summary

FeatureSyntax
Classclass Name { fields; methods }
Methodpub name(self, ...) -> Type { }
Inheritanceclass Child : Parent { }
Abstractabstract class Name { }
Virtualpub virtual method(self) -> Type { }
Constructorpub init(self, ...) -> unit { }
Destructorpub deinit(self) -> unit { }
Instantiatenew ClassName() or new ClassName(args)

Next

Learn about Memory Management with new, release, and defer.

Released under the MIT License.