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#
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: ?Stringmay be left out and starts asnull. - 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:
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:
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:
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:
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:
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:
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:
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.
Related#
- 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.
