Structs and sum types
Two ways to define your own types: a record with named fields (struct) and a tagged union with named variants (sum). Pattern matching becomes powerful here.
Structs: a record of named fields
type Point {
x: Int,
y: Int
}
Construct an instance with curly-brace syntax that names each field, like a JSON object:
fun main(stdio: Stdio)
let p = Point { x: 3, y: 4 }
stdio.println("(${p.x}, ${p.y})")
Field access uses .. Field order does not matter at construction, but every field must be provided. There are no default values; if you want a "zero point", define a function that returns one.
Structs are nominal: two structs with identical field shapes are different types. A Point2D with x: Int, y: Int is not interchangeable with a Vector2 of the same shape. This is deliberate; named types catch confusions at the type level.
Methods on a struct
An impl block attaches methods to a type:
type Point {
x: Int,
y: Int
}
impl Point
fun distance_from_origin(self) -> Float
let sq = to_float(self.x * self.x + self.y * self.y)
return sq // (a real sqrt would live in math; this is illustrative)
Inside an impl, self is the instance. Methods are called on values with .:
fun main(stdio: Stdio)
let p = Point { x: 3, y: 4 }
stdio.println("${p.distance_from_origin()}")
An impl can be inherent (impl Point, methods on Point) or of a trait (impl SomeTrait for Point, satisfying the trait's interface). Traits arrive in chapter 10.
Sum types: one-of-these tagged variants
A sum type lists a closed set of named variants. Each variant can optionally carry a payload:
type Shape =
Circle(Float)
Rectangle(Float, Float)
Square(Float)
Construct a value by naming the variant and passing its payload:
let c = Circle(5.0)
let r = Rectangle(3.0, 4.0)
A variant can also be payloadless:
type Color =
Red
Green
Blue
let c = Red
Pattern matching
The natural way to operate on a sum value is match. Each arm names a variant and binds its payload:
fun area(shape: Shape) -> Float
match shape
Circle(r) ->
return 3.14159 * r * r
Rectangle(w, h) ->
return w * h
Square(side) ->
return side * side
The arms bind r, w, h, side to the corresponding payload of the matched variant. Inside that arm, those are normal local Float values.
match on a sum is checked for exhaustiveness. If you forget a variant, the compiler refuses:
fun area(shape: Shape) -> Float
match shape
Circle(r) -> return 3.14159 * r * r
Rectangle(w, h) -> return w * h
error: non-exhaustive match: missing variant 'Square'
2 | match shape
^
This is one of the most useful guarantees in the language: when you add a new variant to a sum, every match on that sum that did not have a wildcard becomes a compile error until you handle the new case. Refactors that would silently miss a branch in Python become impossible.
Generics: sum types with type parameters
The built-in Option<T> and Result<T, E> are sum types parameterised by their payload type. Your own types can be too:
type Tree<T> =
Leaf
Node(T, Tree<T>, Tree<T>)
A Tree<Int> is a tree of integers, a Tree<String> a tree of strings. The type parameter T is bound at use time.
Try this. Define a type Triangle sum with variants Equilateral(Float) (side length), Right(Float, Float) (legs), and Scalene(Float, Float, Float). Write a function area(t: Triangle) -> Float that handles all three.
The two error modes
First, the exhaustiveness check we already saw: a match on a sum must cover every variant unless you write a _ wildcard arm. The compiler points at the missing one by name.
Second, missing fields when constructing a struct:
type Point {
x: Int,
y: Int
}
fun main(stdio: Stdio)
let p = Point { x: 3 }
stdio.println("${p.x}")
error: struct literal 'Point': missing field 'y'
5 | let p = Point { x: 3 }
^
Every field of the struct must appear in the literal. No defaults; if you want one, write a constructor function that fills the missing fields.
Where you are now
You can model domains with your own types. The next chapter formalises the most common reason a function returns a sum type rather than a plain value: this operation might fail. Option, Result, and the ? operator.