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).
Defining enums
Section titled “Defining enums”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))Color.Green falseShape.Circle(radius=1.0) 3.14Shape.Rect(w=2.0, h=3.0) 6.0Shape.Empty 0.0true- Variants are always written with the enum’s name:
Color.Red,Shape.Circle(1.0). (The exception is the built-inOptionandResult, whoseSome,None,OkandErrstand alone; see Option and Result.) - Construct a variant with data positionally or by keyword, like a struct:
Shape.Rect(2.0, 3.0)orShape.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 noselffor a static method. An enum’s fields can’t be changed in place; aref selfmethod changes the value by assigning a whole new one (self = Light.Off). - Enums are values: copying one copies it.
Reading variant data
Section titled “Reading variant data”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 errorerror: could not compile due to 1 errorRecursive enums
Section titled “Recursive enums”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)14Expr.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.
Patterns
Section titled “Patterns”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")nonea fewinvalid (-5)manyorigin on an axis on an axis on the diagonal at 2 elsewherezero1 + 2double negation of Expr.Num(value=5)a sum starting with Expr.Num(value=1)true falsegoing| 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.
Exhaustiveness
Section titled “Exhaustiveness”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 errorerror: could not compile due to 1 errorThe 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 errorerror: could not compile due to 1 errorA case that can never be reached because earlier cases already match everything it would is the warning W0002.
Summary
Section titled “Summary”enum Name:lists variants: plain tags orVariant(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, andifguards. matchmust be exhaustive (E0320); guarded cases don’t count; integers and strings needcase _:.