Skip to content

The language

Enums and match

An enum is a type whose value is one of a fixed set of variants, each of which may carry its own named fields. You read a variant’s data with match, and every match must handle every possible value: the compiler checks this and shows you a value you missed (E0320).

A variant is a plain tag (Red) or carries fields (Circle(radius: float)). Enums can have methods, defined inside them like a struct’s.

enum Color:
Red
Green
Blue
enum Shape:
Circle(radius: float)
Rect(w: float, h: float)
Empty
fn area(self) -> float:
match self:
case Shape.Circle(r):
return 3.14 * r * r
case Shape.Rect(w, h):
return w * h
case Shape.Empty:
return 0.0
fn main():
let c = Color.Green
print(c, c == Color.Red)
let shapes = [Shape.Circle(1.0), Shape.Rect(w=2.0, h=3.0), Shape.Empty]
for s in shapes:
print(s, s.area())
print(shapes[1] == Shape.Rect(2.0, 3.0))
Output
Color.Green false
Shape.Circle(radius=1.0) 3.14
Shape.Rect(w=2.0, h=3.0) 6.0
Shape.Empty 0.0
true
  • Variants are always written with the enum’s name: Color.Red, Shape.Circle(1.0). (The exception is the built-in Option and Result, whose Some, None, Ok and Err stand alone; see Option and Result.)
  • Construct a variant with data positionally or by keyword, like a struct: Shape.Rect(2.0, 3.0) or Shape.Rect(w=2.0, h=3.0).
  • Enum values print as Enum.Variant(field=value, ...) and compare with == and !=.
  • Methods work as on structs: self, ref self, or no self for a static method. An enum’s fields can’t be changed in place; a ref self method changes the value by assigning a whole new one (self = Light.Off).
  • Enums are values: copying one copies it.

Variant data can only be read with match, never with .field, because the field exists only in some variants:

enum Shape:
Circle(radius: float)
Empty
fn main():
let s = Shape.Circle(2.0)
print(s.radius)
error[E0205]: enum `Shape` has no fields
--> main.br:7:13
|
7 | print(s.radius)
| ^^^^^^ use `match` to read the data in a variant
= run `brine explain E0205` for more about this error
error: could not compile due to 1 error

A variant may contain its own enum, so trees and expressions need no extra machinery:

enum Expr:
Num(value: int)
Add(left: Expr, right: Expr)
Mul(left: Expr, right: Expr)
fn eval(e: Expr) -> int:
match e:
case Expr.Num(v):
return v
case Expr.Add(l, r):
return eval(l) + eval(r)
case Expr.Mul(l, r):
return eval(l) * eval(r)
fn main():
let e = Expr.Add(Expr.Num(2), Expr.Mul(Expr.Num(3), Expr.Num(4)))
print(eval(e))
print(e)
Output
14
Expr.Add(left=Expr.Num(value=2), right=Expr.Mul(left=Expr.Num(value=3), right=Expr.Num(value=4)))

Enums can be generic too (enum Tree[T]:); see generics.

match value: is followed by case pattern: blocks. The first case whose pattern matches runs; there is no fall-through. match is a statement (it doesn’t produce a value), so assign or return inside the cases.

enum Shape:
Circle(radius: float)
Rect(w: float, h: float)
Empty
enum Expr:
Num(value: int)
Add(left: Expr, right: Expr)
Neg(inner: Expr)
fn size(n: int) -> str:
match n:
case 0: # a literal
return "none"
case 1 | 2 | 3: # alternatives
return "a few"
case k if k < 0: # a capture with a guard
return f"invalid ({k})"
case _: # anything else
return "many"
fn where(p: (int, int)) -> str:
match p:
case (0, 0):
return "origin"
case (0, _) | (_, 0): # alternatives (no names inside)
return "on an axis"
case (x, y) if x == y:
return f"on the diagonal at {x}"
case _:
return "elsewhere"
fn describe(e: Expr) -> str:
match e:
case Expr.Num(0): # a literal inside a variant
return "zero"
case Expr.Add(Expr.Num(a), Expr.Num(b)): # nested variants
return f"{a} + {b}"
case Expr.Neg(Expr.Neg(inner=x)): # keyword field pattern
return f"double negation of {x}"
case Expr.Add(left=l): # just the fields you need
return f"a sum starting with {l}"
case _:
return "something else"
fn tall(s: Shape) -> bool:
match s:
case Shape.Rect(h=h) if h > 10.0:
return true
case _:
return false
fn main():
for n in [0, 2, -5, 99]:
print(size(n))
print(where((0, 0)), where((0, 7)), where((3, 0)), where((2, 2)), where((1, 3)))
print(describe(Expr.Num(0)))
print(describe(Expr.Add(Expr.Num(1), Expr.Num(2))))
print(describe(Expr.Neg(Expr.Neg(Expr.Num(5)))))
print(describe(Expr.Add(Expr.Num(1), Expr.Neg(Expr.Num(2)))))
print(tall(Shape.Rect(1.0, 20.0)), tall(Shape.Circle(30.0)))
match "go":
case "stop":
print("stopping")
case "go":
print("going")
case _:
print("unknown command")
Output
none
a few
invalid (-5)
many
origin on an axis on an axis on the diagonal at 2 elsewhere
zero
1 + 2
double negation of Expr.Num(value=5)
a sum starting with Expr.Num(value=1)
true false
going
Pattern Matches
_ anything, and binds nothing
name anything, and binds it to name (a capture)
0, "go", true that integer, string or bool literal
(a, 0) a tuple whose parts match, element by element
p1 | p2 | p3 any of the alternatives; they can’t bind names (E0321), so use _ inside them
Shape.Rect(w, _) that variant, with its fields matched in order
Expr.Add(left=l) that variant, with the named fields matched and the rest ignored
Expr.Add(Expr.Num(a), _) variants nested in variants, to any depth
pattern if condition the pattern, and then only if the condition (which can use its captures) is true

A capture is a new variable, so it follows the no-shadowing rule: it can’t reuse the name of a variable that is already in scope. Captures in different cases may share a name.

Every match must handle every possible value. When it doesn’t, the error shows a value that no case handles, so a new enum variant can never slip through unnoticed:

enum Shape:
Circle(radius: float)
Rect(w: float, h: float)
Empty
fn area(s: Shape) -> float:
match s:
case Shape.Circle(r):
return 3.14 * r * r
case Shape.Empty:
return 0.0
fn main():
print(area(Shape.Empty))
error[E0320]: this `match` doesn't handle every possible value
--> main.br:7:11
|
7 | match s:
| ^ of type `Shape`
|
= note: not handled: `Shape.Rect(_, _)`
= help: add `case Shape.Rect(_, _):`, or a catch-all `case _:`
= run `brine explain E0320` for more about this error
error: could not compile due to 1 error

The check looks inside tuples and nested variants too: matching (true, _) and (false, true) on a (bool, bool) reports (false, false) as not handled.

Cases with a guard don’t count, because the guard may be false. Integers and strings have too many values to list, so a match on them always needs case _: (or a plain capture, which matches everything):

fn main():
let n = 4
match n:
case 0:
print("zero")
case k if k > 0:
print("positive")
error[E0320]: this `match` doesn't handle every possible value
--> main.br:3:11
|
3 | match n:
| ^ of type `int`
|
= note: cases with an `if` guard don't count, since the guard may be false
= help: add a catch-all `case _:` at the end
= run `brine explain E0320` for more about this error
error: could not compile due to 1 error

A case that can never be reached because earlier cases already match everything it would is the warning W0002.

  • enum Name: lists variants: plain tags or Variant(field: Type, ...). Enums can have methods, be recursive and be generic.
  • Write variants with the enum’s name, in expressions and in patterns.
  • Read variant data with match, never .field.
  • Patterns: _, captures, literals, tuples, | alternatives (no names inside), variants with positional or keyword fields, nested to any depth, and if guards.
  • match must be exhaustive (E0320); guarded cases don’t count; integers and strings need case _:.