Records, enums, match
A record is a shape. An enum is a choice. match covers every case, and the compiler counts them.
Records
A record type lists fields. Compatibility is by shape: a value with more fields fits where fewer are wanted, with no cast and no base class. This is width subtyping, and it is the whole inheritance story.
Fields of a mut binding are assigned in place. A record that owns heap data — a String field, for example — is moved by an assignment, so copy() is how both sides keep a value.
// A record type is a shape. A value with more fields fits where fewer are
// wanted, with no cast and no inheritance.
import { joinWith } from "std/strings"
type Named = { name: String }
type User = { name: String, age: Int64 }
fn greet(who: Named) -> String {
return "hello, \{who.name}"
}
export fn demo() -> String {
let u = User { name: "Ada", age: 36 }
let mut next = u.copy() // a copy, because both `u` and `next` are read below
next.age = next.age + 1
return joinWith([greet(u), "was \{u.age}", "now \{next.age}"], "\n")
}
fn main() -> Int64 {
print(demo())
return 0
}
hello, Ada
was 36
now 37
Enums
An enum value is exactly one variant, and a variant can carry a payload. There is no tag field to keep in step with a union, because the tag is the type.
match must cover every variant. Leave one out and the file does not compile. That is what makes an enum safe to extend: add a variant, and the compiler lists every place that has to answer for it.
// A value is exactly one variant. A variant may carry a payload, and `match`
// must cover every one — leave an arm out and the file does not compile.
import { joinWith } from "std/strings"
type Shape = enum {
Circle(Int64),
Rect(Int64, Int64),
Point }
fn describeShape(s: Shape) -> String {
return match s {
Circle(r) => "circle of radius \{r}",
Rect(w, h) => "rect \{w} by \{h}",
Point => "a point",
}
}
export fn demo() -> String {
let shapes: Array<Shape> = [Circle(2), Rect(3, 4), Point]
let mut out: Array<String> = []
for s in shapes {
out.push(describeShape(s))
}
return joinWith(out, "\n")
}
fn main() -> Int64 {
print(demo())
return 0
}
circle of radius 2
rect 3 by 4
a point
match expressions
match answers with a value, so a binding takes it directly and there is no mutable slot to fill in each arm.
if let reads one variant when the others need no arm. while let does the same in a loop, and stops when the pattern stops matching.
// `match` is an expression: it answers with a value, so a binding can take it
// directly. `if let` reads one variant when the others need no arm of their own.
import { joinWith, indexOf } from "std/strings"
type Level = enum {
Info,
Warn(String) }
fn found(haystack: String, needle: String) -> String {
if let Some(at) = indexOf(haystack, needle) {
return "\{needle} at byte \{at}"
}
return "\{needle} is not there"
}
export fn demo() -> String {
let l: Level = Warn("disk is filling")
let line = match l {
Info => "nothing to say",
Warn(what) => "warning: \{what}",
}
return joinWith([line, found("hello, world", "world"), found("hello, world", "zebra")], "\n")
}
fn main() -> Int64 {
print(demo())
return 0
}
warning: disk is filling
world at byte 7
zebra is not there