Unions
Use unions to describe a closed set of states, with or without associated data.
Payload-free Unions
Variants without payloads form a closed set of labels. The compiler rejects unknown labels, and exhaustive matching makes each case explicit. Adding a variant identifies the matches that need updating.
union Direction:
North
East
South
West
pure func direction_name(direction: Direction) -> String:
match direction:
North: "north"
East: "east"
South: "south"
West: "west"
func main(args: List[String]) -> Void:
print(direction_name(North)) -- north
print(direction_name(West)) -- west
Data-carrying Unions
Variants can carry different data: Running(Int) holds progress, while Done(String) holds a path. A value has exactly one variant, so each state carries only the data that belongs to it. Match the variant to access its payload.
union Download:
Queued
Running(Int)
Done(String)
Failed(String)
func main(args: List[String]) -> Void:
status: Download = Running(40)
match status:
Queued: print("queued")
Running(percent): print("${percent}% complete") -- prints: 40% complete
Done(path): print("saved ${path}")
Failed(message): print("failed: ${message}")
Generic Unions
A union can take type parameters. In Lookup[T], Found carries a value of type T, while Missing carries no data. Lookup[Int] and Lookup[String] share the same structure with different payload types. The generic value_or function works with either, keeping the fallback and result the same type as the payload.
union Lookup[T]:
Missing
Found(T)
pure func value_or[T](lookup: Lookup[T], fallback: T) -> T:
match lookup:
Missing: fallback
Found(value): value
func main(args: List[String]) -> Void:
score: Lookup[Int] = Found(42)
name: Lookup[String] = Found("Ada")
absent: Lookup[String] = Missing
print(value_or(score, 0).to_string()) -- prints: 42
print(value_or(name, "guest")) -- prints: Ada
print(value_or(absent, "guest")) -- prints: guest
Recursive Unions
A union can refer to itself in a variant's payload. Here an Expression is either a number or the sum of two more expressions, allowing trees of arbitrary depth. Use a non-recursive variant to end each branch.
union Expression:
Number(Int)
Add(Expression, Expression)
pure func evaluate(expression: Expression) -> Int:
match expression:
Number(value): value
Add(left, right): evaluate(left) + evaluate(right)
func main(args: List[String]) -> Void:
expression: Expression = Add(Number(2), Add(Number(3), Number(4)))
print(evaluate(expression).to_string()) -- prints: 9
Fixed Unions
fixed union makes allocation-free union storage a compiler-checked contract. Constructing, copying, passing, and returning an eligible value require no separate union allocation or hidden payload box. Fixed and non-fixed unions receive the same optimizations for identical bodies; fixed requires the guarantee, rather than selecting a different optimizer.
fixed union Command:
Idle
Move(Int)
pure func distance(command: Command) -> Int:
match command:
Idle: 0
Move(steps): steps
func main(args: List[String]) -> Void:
print(distance(Idle).to_string()) -- prints: 0
print(distance(Move(12)).to_string()) -- prints: 12
Fixed Eligibility
Payload-free variants and scalar payloads such as Int, Float, and Bool are eligible. Every variant and nested payload must satisfy the fixed-storage rules, even if never constructed. Nested records and unions must also be fixed. Non-fixed records and unions are ineligible even when all their fields are scalar. Direct or nested String payloads are ineligible, even when the string already exists. Directly recursive unions such as Expression are ineligible because they have no finite inline layout. Use non-fixed union for these cases. A fixed union need not live on the stack: it can live inside an already allocated container, and growing that container may still allocate.
Example
union Download:
Queued
Running(Int)
Done(String)
Failed(String)
pure func describe(download: Download) -> String:
match download:
Queued: "queued"
Running(percent): "running " + percent.to_string()
Done(path): "done: " + path
Failed(msg): "failed: " + msg
func main(args: List[String]) -> Void:
print(describe(Running(40))) -- prints: running 40
Try it
blorp run download.brp