Variants and match
Defining the cases of a value and handling them with match.
A record contains all its fields: a point has an x and a y. A variant represents exactly one declared case. For example, a token, an element of the text the compiler reads, can be a number, a word or a plus sign. Some cases contain additional data.
Declaring one
variant Token
number
value: i64
word
text: str
plus
times
open
close
The type has six cases. number and word contain data declared as fields indented below the case name. The other four have no fields: plus is already a complete value.
Reading one: match
match identifies a variant’s case and lets you access its fields in the same branch:
# What a token is called in a message.
function name_of(t: Token) -> str
return match t
.number _ => "a number"
.word _ => "a word"
.plus => "`+`"
.times => "`*`"
.open => "`(`"
.close => "`)`"
Each branch names the case with a leading dot, followed by => and the result. If the case contains data, you can give it a name: in .number n, n.value is the number inside. Here the data is unused, so the branch uses _, which declares no name and can be repeated.
match is an expression: it has a value, which is why this whole
function is one return. It can also stand alone as a statement
when the arms do something rather than produce something.
Every case, or it does not compile
Leave one out and the build stops. This is the file above with its last arm deleted:
$ heroes check tokens.hero
error[non_exhaustive]: this `match` does not cover `.close`
at tokens.hero:19:12
|
19 | return match t
| ^
A _ branch is forbidden for variants
For a variant, Heroes forbids a _ branch that would handle all unlisted cases. The message explains why:
$ heroes check tokens.hero
error[wildcard_on_variant]: `_` is not allowed as an arm over `Token` — name every case, so that adding one breaks this `match` instead of silently falling through
at tokens.hero:25:9
|
25 | _ => "something else"
| ^
You can use _ in a match on an i64 or a str to handle all unlisted values. For these types, writing a branch for every value would be impractical.
Adding a case requires updating each match
Add a seventh case, minus, to Token and check the program again:
$ heroes check tokens.hero
error[non_exhaustive]: this `match` does not cover `.minus`
at tokens.hero:20:12
|
20 | return match t
| ^
error[non_exhaustive]: this `match` does not cover `.minus`
at tokens.hero:30:12
|
30 | return match t
| ^
error[non_exhaustive]: this `match` does not cover `.minus`
at tokens.hero:38:12
|
38 | return match t
| ^
The compiler reports three errors, one for each match that does not handle the new case. You must decide what to call the minus sign, its precedence and whether it is an operator. Compilation can continue once all cases are handled.
Two cases, one answer
When several cases produce the same result, | lets you list them in one branch:
function is_operator(t: Token) -> bool
return match t
.plus | .times => true
.number _ | .word _ | .open | .close => false
Every case remains explicit. Adding minus therefore requires updating this function too, deciding whether it should return true or false for the new case.
Making one
function main()
# The annotation is required and it is not ceremony: a case says which
# case it is, never which variant, so a literal list of them has nothing
# to infer from. Name the type once and the whole list is checked.
line: [Token] = [.open, .number(value: 12), .plus, .word(text: "x"), .close]
for t in line
print(name_of(t))
$ heroes run examples/gallery/02-tokens.hero
`(`
a number
`+`
a word
`)`
Write a case without fields as .plus. For a case with fields, use .number(value: 12), naming the fields as for a record. The leading dot identifies the case, but the compiler also needs its type. The annotation : [Token] supplies it for every element in the list.
Next: Failure is a value, which is this chapter’s idea applied to the one case every program has: it worked, or it did not. Back to the chapter list. ← Previous: Records and functions
Run a program of your own: it takes four commands. Or take the whole language in one file, and every program already written in it.