Describe alternatives with enums
An enum defines alternatives, each of which may carry typed data. Match its variants to extract that data:
enum Reading:
Missing
Value(i64)
Invalid(str)
def describe(reading: Reading) -> str:
match reading:
case Reading.Missing:
"no reading"
case Reading.Value(number):
"positive" if number > 0 else "nonpositive"
case Reading.Invalid(reason):
reason
def main() -> Result[(), IoError]:
print(describe(Reading.Missing))?
print(describe(Reading.Value(42)))?
print(describe(Reading.Invalid("sensor offline")))?
Ok(())
no reading
positive
sensor offline
Enum payloads are stored inline: Reading.Value(42) needs no allocation or ?.
Variants without data omit parentheses. Payloads may have several positions,
such as Pair(i64, str). Case bindings are immutable and local to that case.
Use _ to ignore a payload position or a final case _: to cover remaining
variants; case _ as name: also binds the remaining value. Duplicate or missing cases are errors:
enum Switch:
On
Off
def main() -> Result[(), IoError]:
match Switch.On:
case Switch.On:
print("on")?
Ok(())
non-exhaustive match; missing Switch.Off
A final match produces the function’s result; cases can also return early or break/continue a loop. A match can also be the value of a binding. Separately declared enums remain distinct types, even with identical variants.
A payload position can hold another pattern instead of a name, so one case
reaches into an enum stored inside an enum. | joins patterns that share an
arm; every alternative must bind the same names:
enum Shape:
Dot
Circle(i64)
Rect(i64, i64)
enum Slot:
Empty
Full(Shape)
def width(slot: Slot) -> i64:
match slot:
case Slot.Full(Shape.Circle(size) | Shape.Rect(size, _)):
size
case Slot.Full(Shape.Dot) | Slot.Empty:
0
def main() -> Result[(), IoError]:
print(width(Slot.Full(Shape.Rect(3, 4))))?
print(width(Slot.Full(Shape.Circle(5))))?
print(width(Slot.Empty))?
Ok(())
3
5
0
Coverage is checked through the nested patterns: leaving out
Slot.Full(Shape.Dot) is an error that names it.
Payloads can contain strings, collections, and other enums. Matching an enum
with mutable payloads consumes it and transfers those payloads; use
match copy(value)? to preserve the owner.
An enum can keep behavior beside its variants. A method’s bare self is a
shared borrow, so matching it borrows the payload. The same method can satisfy
a structural protocol:
protocol Numbered:
def number(self) -> i64:
pass
enum Reading:
Missing
Value(i64)
def number(self) -> i64:
match self:
case Reading.Missing:
0
case Reading.Value(value):
*value
def number[T: Numbered](value: &T) -> i64:
value.number()
def main() -> Result[(), IoError]:
reading = Reading.Value(42)
print(number(&reading))?
print(reading.number())?
Ok(())
42
42
Use pub def when callers in other modules need the method, and
self: &mut Reading when it changes a payload. Variant and method names must
be distinct. The formatting lesson uses an enum
method to produce a message while preserving the error data.