Docs/Types

Classes

Group state and methods in an object.

On this page

A class groups properties and methods into an object. Class values are references: assigning one shares the same object.

Define a class#

Valk
class Task {
    title: String
    done: bool (false)
    note: ?String

    fn complete() {
        this.done = true
    }

    fn summary() String {
        return this.done ? "Done: %{this.title}" : "Todo: %{this.title}"
    }
}

fn main() {
    let task = Task { title: "Write the guide" }
    task.complete()
    println(task.summary()) // Done: Write the guide
}
  • A default goes in parentheses after the property type: done: bool (false).
  • A nullable property such as note: ?String may be left out and starts as null.
  • Every other property must be given in the initializer.
  • Methods reach the object through this.

Create an object#

Type { ... } names each property. Where the type is already known, .{ ... } is enough:

Valk
class Point {
    x: int (0)
    y: int (0)
}

fn show(point: Point) {
    println("%{point.x},%{point.y}")
}

fn main() {
    let a = Point { x: 1, y: 2 }
    let b: Point = .{ x: 5 }
    show(a)          // 1,2
    show(b)          // 5,0
    show(.{ y: 9 })  // 0,9
}

Static functions and constructors#

A static fn belongs to the class instead of an object. SELF names the class itself, which is handy for constructors:

Valk
class Account {
    owner: String
    balance: int

    static fn open(owner: String) SELF {
        return SELF { owner: owner, balance: 0 }
    }
}

fn main() {
    let account = Account.open("Ada")
    let other: Account = .open("Sam") // `.` uses the expected type
    println(account.owner + " " + other.owner) // Ada Sam
}

Getters and setters#

A getter is read like a property. A setter with the same name runs on assignment, including += and other compound operators:

Valk
class Temperature {
    celsius: float

    get fahrenheit: float {
        return this.celsius * 9.0 / 5.0 + 32.0
    }
    set fahrenheit(value: float) {
        this.celsius = (value - 32.0) * 5.0 / 9.0
    }
}

fn main() {
    let t = Temperature { celsius: 0.0 }
    t.fahrenheit = 212.0
    println(t.celsius) // 100
    t.fahrenheit -= 18.0
    println(t.celsius) // 90
}

Operators#

A method marked with an operator flag defines that operator for the class:

Valk
class Money {
    cents: int

    + fn add(other: Money) Money $add {
        return Money { cents: this.cents + other.cents }
    }
    + fn equals(other: Money) bool $eq {
        return this.cents == other.cents
    }
    + fn less(other: Money) bool $lt {
        return this.cents < other.cents
    }
}

fn main() {
    let a = Money { cents: 150 }
    let b = Money { cents: 250 }
    let total = a + b
    println(total.cents)                  // 400
    println(a == Money { cents: 150 })    // true
    println(a < b)                        // true

    let list = Array[Money]{ b, a }
    list.sort()                           // uses $lt
    println(list[0].cents)                // 150
}
Flag Operator
$add $sub $mul $div + - * / and += -= *= /=
$eq == and !=
$lt $gt $lte $gte ordering; one is enough, the others follow

Mark operator methods + so standard containers such as Array and HashMap can call them. A class used as a HashMap key also needs a fn hash() uint method, and equal values must give equal hashes. Without $eq, objects compare by identity.

Add methods to an existing type#

extend adds methods to a class you did not write, including built-in ones:

Valk
extend String {
    fn shout() String {
        return this.upper() + "!"
    }
}

fn main() {
    println("hello".shout()) // HELLO!
}

Copy an object#

Assignment shares the object. copy x makes a shallow copy and clone x a deep copy:

Valk
class Team {
    name: String
    members: Array[String] (.{})
}

fn main() {
    let a = Team { name: "red" }
    let b = copy a   // shares a.members
    let c = clone a  // has its own members array
    b.members.append("Ada")
    println(a.members.length) // 1
    println(c.members.length) // 0
}

A class can define its own fn copy() SELF or fn clone() SELF. Without one, properties are copied one by one, which needs every property to be unmarked or +. See Copies & references.

Release native resources#

gc_free() runs when an unused object is collected. Use it only as a safety net for native resources; it cannot allocate, throw, or use other objects, and you do not know when it runs:

Valk
class Handle {
    id: int
    closed: bool (false)

    fn close() {
        if this.closed : return
        this.closed = true
        println("closed %{this.id}")
    }

    fn gc_free() {
        // last-resort cleanup of native state goes here
    }
}

fn main() {
    let handle = Handle { id: 1 }
    defer handle.close()
    println("working")
}

For files, sockets and locks, call close() yourself, usually with defer.

  • Control who can read or change a property with visibility markers.
  • Accept several classes through one set of methods with interfaces.
  • Share method bodies between classes with traits.