Modeling data
Choose between an object, a value, and a set of alternatives.
On this page
The shape of your data determines which language feature is useful. A class gives an object identity and behavior. A struct groups fields into a value. An enum names a fixed set of choices, while a tagged union allows those choices to carry different types.
Put related state in a class#
class Task {
title: String
done: bool (false)
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())
}
A class initializer names its properties. A default goes in parentheses after the property type. Instance methods access the receiver with this.
Assigning a class value to another variable refers to the same object. It does not make a copy of its properties.
Use a struct for a value#
struct Point {
x: int
y: int
}
fn main() {
let origin = Point { x: 0, y: 0 }
let cursor = origin
cursor.x = 10
println(origin.x) // 0
println(cursor.x) // 10
}
Struct assignment copies the fields. If a field itself holds a class reference, the copied field still refers to that same object. A struct is not an automatic deep copy of everything reachable from it.
Use a class when shared object identity is useful, and a struct when values should be copied as part of assignment or argument passing.
Represent alternatives#
An enum such as enum State { waiting active complete } is useful when every case is just a named choice. A tagged union can hold different types of data:
union Setting : String | int | bool {}
fn display(value: Setting) String {
return match value : String {
String as text => text
int as number => number.to(String)
bool as enabled => enabled ? "on" : "off"
}
}
fn main() {
let setting: Setting = 8080
println(display(setting))
}
as binds the payload in a match branch. Each branch here returns a String, and every alternative is handled. The compiler can check that a match covers the union's alternatives.
Share an interface#
An interface describes operations that different classes can provide:
interface Described {
fn describe() String;
}
class Book is Described {
title: String
fn describe() String {
return "Book: %{this.title}"
}
}
fn print_description(item: Described) {
println(item.describe())
}
fn main() {
print_description(Book { title: "A small guide" })
}
A class lists its interfaces after is. Its methods must satisfy the declared signatures. The interface gives a caller access to that common behavior without requiring it to know the concrete class.
Only classes implement interfaces. For reusable code that should retain a concrete type, consider generics.