Enums and match
An enum is a type with a fixed set of named cases. A value of the
enum holds exactly one of them. Use an enum where you would otherwise pass an
integer code or a string, so the compiler checks that only the listed cases
flow through, and that a match handles each one.
There are two kinds:
- In an enum whose cases carry nothing (
Red,Green), each case is an integer. Such an enum is a value type, likeint. - When a case carries values (
Circle(radius: int)), the enum is a reference type, like a struct. See Cases that carry values.
# An enum names a fixed set of cases; a slot of the enum type holds one.
enum Suit {
Clubs,
Diamonds,
Hearts,
Spades,
}
# A representation sets the stored integer type; a case may give its value.
enum Status: u8 {
Ok,
NotFound = 44,
Failed,
}
struct Card {
Suit suit;
int rank = 1;
}
main(): int {
var card = Card();
assert(card.suit == Suit.Clubs, "a slot starts at the case whose value is 0");
card.suit = Suit.Hearts;
assert(card.suit != Suit.Spades, "cases compare with == and !=");
# 'as' turns a case into its integer; the reverse is not allowed.
assert((Suit.Hearts as int) == 2, "cases number from 0");
assert((Status.Failed as int) == 45, "numbering continues after an explicit value");
assert(sizeof<Status>() == 1, "Status is stored as a u8");
return 0;
}
Declaring an enum
enum Name { ... } lists the cases, separated by commas; a comma after the
last case is optional. Case names are PascalCase, like types (see
naming conventions). A case may share a type’s name
(String(value: String)); a string literal in the enum’s methods is still
a String, and String(...) there calls the case.
Each case is an integer. The first case is 0 and each case after it is one
more than the one before. A case can give its own value with = value, and
numbering continues from there:
enum Status: u8 {
Ok, # 0
NotFound = 44, # 44
Failed, # 45
}
A value must be an integer constant: a literal such as -1 or 0x20, or a
character such as 'y'. Two cases cannot have the same value.
The type after the colon is the representation, the integer type the
enum is stored as. It can be any integer type (u8, short, int, long,
and so on). Without one, the enum is stored as an int. Every case value
must fit in the representation.
A case can carry a [Target] attribute, so a
platform can have cases that others lack. A dropped case does not renumber
the others.
Using enum values
This section is about enums whose cases carry nothing.
Name.Case is a value of the enum type. Such an enum is a value type, like int:
it is copied, not reference counted, and it can be stored in locals, fields,
arrays and module-scope constants, and passed to and returned from functions.
==and!=compare two values of the same enum. Other operators apply only when the enum declares them (see Operators and indexers).- An enum does not mix with integers or with other enums:
int x = Color.RedandColor c = 0are errors, and so is comparing aColorwith aSize. value as int(or any integer type) gives a case’s integer value. The reverse,1 as Color, is an error, since the integer may not name a case; name the case, or usefrom_value.- A slot of an enum type that is not given a value (a field, or a local declared without one) holds the case whose value is 0, or the first case when no case is 0.
sizeof<Name>()is the size of the representation.- An enum works wherever an integer does: as a
HashMapkey orHashSetelement, in anAtomic<Name>, and in anexternfunction’s signature, where C sees the representation (anintunless the enum names another).
Cases that carry values
A case can carry values, declared like parameters: Circle(radius: int).
An enum with such a case is a reference type: each value is an object,
reference counted like a struct.
# Cases that carry values: each case is a constructor, and a `match`
# pattern binds the values of the case it matches.
enum Shape {
Circle(radius: int),
Rect(width: int, height: int),
Square(side: int),
Point,
area(): int {
match (this) {
Circle(r) => { return 3 * r * r; }
Rect(w, h) => { return w * h; }
Square(s) => { return s * s; }
Point => { return 0; }
}
return 0;
}
}
# A generic enum can name itself: a list is empty, or a value and the rest.
enum List<T> {
Empty,
Cons(head: T, tail: List<T>),
}
length(list: List<String>): int {
match (list) {
Empty => { return 0; }
# `_` skips a value.
Cons(_, tail) => { return 1 + length(tail); }
}
return 0;
}
main(): int {
var shapes = Array<Shape>();
shapes.append(Shape.Circle(2));
shapes.append(Shape.Rect(3, 4));
shapes.append(Shape.Point);
var total = 0;
for (var s in shapes) {
total += s.area();
}
assert(total == 24, "12 + 12 + 0");
# Several patterns in one arm bind the same names.
var side = 0;
match (Shape.Square(5)) {
Rect(w, _), Square(w) => { side = w; }
else => {}
}
assert(side == 5, "a square's side");
var names = List<String>.Cons("a", List<String>.Cons("b", List<String>.Empty));
assert(length(names) == 2, "two names");
assert(names.name() == "Cons", "name() spells the case");
return 0;
}
Name.Case(values...)makes a new value, like a constructor call, andName.Casealone is a case that carries nothing. An enum’s values are made only by its cases:Shape(...)is an error.- A case that carries values must be given them:
Shape.Circlewithout(radius)is an error. - The values are fixed once made. There is no way to change them, so a value can be shared freely. To change one, make a new value.
- Each case that carries nothing is a single object that every use shares. It is never freed and costs no allocation, however often it is used.
- A slot of the enum type that is not given a value holds null, as a struct
slot does.
===and!==compare identity, and so does==, unless the enum is[Equatable]. - Mark the enum
[Equatable]to derive==andhashfrom its cases: two values are equal when they hold the same case with equal values, so they work asHashMapandHashSetkeys. - An enum can have type parameters (
enum List<T>), and its cases can hold the enum itself, directly or through other types, so lists and trees are enums. - The enum can be
[Shared]when every value its cases carry is thread-safe (see What threads can share). An enum whose cases carry nothing is stored as an integer and is always thread-safe. It can conform to a trait withextend Name with Trait, like a struct, and its methods can come from anextendblock too. - Cases cannot have explicit values (
= 3), and the enum has no representation type (: u8), since its values are not integers.
Value enums
An enum whose cases carry values can be marked [Value], like a
value struct. Each value is then stored inline in
whatever holds it, and copied when assigned, passed or returned, instead of
being an object. Making one allocates nothing, which suits small enums that
are made often, such as Optional and
Result, which are both value enums.
[Value]
enum Shape {
Circle(radius: int),
Rect(width: int, height: int),
Point,
}
- Its cases share one storage: a value is as large as its largest case, plus
the tag that says which case it holds (
sizeof<Shape>()is 12: the tag, then room forRect’s twoints, whichCircle’sradiusreuses). - A value enum is never null. A slot that is not given a value holds the
first case, with every value it carries zero,
false, or null: an unsetShapeisCircle(0). ==compares the cases and their values;===,!==and__nullare errors.- Its cases cannot hold the enum itself, since the value would be infinitely large. Use a reference enum for lists and trees.
- Everything else a value struct cannot do applies too, and so does boxing when it flows into a trait value.
Methods
An enum can declare methods after its cases. Inside a method, this is the
value the method was called on, and a bare call of another method of the
enum (next()) calls it on this. A static method is called on the enum
itself (Light.first()). A method can be generic (choose<T>), with its
type arguments inferred or written out (light.choose<String>(...)).
# An enum can declare methods after its cases; `this` is the value.
enum Light: u8 {
Red,
Amber,
Green,
may_go(): bool {
return this == Light.Green;
}
next(): Light {
match (this) {
Red => { return Light.Green; }
Amber => { return Light.Red; }
Green => { return Light.Amber; }
}
return this;
}
# A bare call of another method acts on `this`.
after_two(): Light {
return next().next();
}
# A method can be generic, like any function.
choose<T>(stop: T, go: T): T {
return may_go() ? go : stop;
}
static first(): Light {
return Light.Red;
}
}
main(): int {
var light = Light.first();
assert(!light.may_go(), "red means stop");
assert(light.next().may_go(), "green follows red");
assert(light.after_two() == Light.Amber, "then amber");
assert(light.choose("wait", "walk") == "wait" && Light.Green.choose(0, 1) == 1, "a generic method");
# Every enum has name() and from_value().
assert(light.name() == "Red", "name() spells the case");
var found = Light.from_value(2);
assert(found.is_ok() and found.value() == Light.Green, "2 is Green");
assert(!Light.from_value(7).is_ok(), "no case has the value 7");
# Enums key hash maps and fill atomics, like integers.
var waits = HashMap<Light, int>();
waits[Light.Red] = 30;
assert(waits[Light.Red] == 30, "an enum is a hash key");
return 0;
}
For an enum whose cases carry nothing, this is passed by value, like an
int: a method cannot change the value it was called on. Return a new case
instead, as next() does. Such an enum can also declare a trait with
extend Light with Describe { ... }, which makes it meet the constraint
T: Describe (see
numbers, bool and enums);
it is still never a trait value. For an enum whose cases carry values, this is
the object, as in a struct’s method; its values still cannot change.
Operators and indexers
An enum can declare operators and an
indexer the way a struct does: a method named after
the operator (<(other: Level): bool) or []. For an enum whose cases
carry nothing, a < b calls Level.< with a as this.
# An enum can declare operators and an indexer, as a struct does.
enum Level: u8 {
Low,
Mid,
High,
# `a < b` calls `Level.<`: the cases rank in order.
<(other: Level): bool {
return (this as int) < (other as int);
}
# `level + 1` moves up a level, stopping at the top.
+(steps: int): Level {
var next = Level.from_value((this as int + steps) as u8);
return next.is_ok() ? next.value() : Level.High;
}
# `-level` mirrors it.
-(): Level {
return match (this) {
Low => Level.High,
Mid => Level.Mid,
High => Level.Low,
};
}
# `level[i]` reads, like any indexer.
[](bit: int): bool {
return ((this as int) >> bit) & 1 == 1;
}
}
main(): int {
var level = Level.Low;
assert(level < Level.Mid && !(Level.High < level), "cases rank in order");
assert(level + 1 == Level.Mid && level + 5 == Level.High, "a step up");
assert(-level == Level.High, "mirrored");
assert(Level.Mid[0] && !Level.Mid[1] && Level.High[1], "bits");
return 0;
}
Its values never change and it already compares with == and !=, so
such an enum cannot declare ==, !=, a compound assignment (+=), an
indexer that assigns ([](index, value)) or the truth test !!, and its
operators are not static.
Properties
An enum can also declare computed properties after its
cases, var name: T { get { ... } }, which are read like fields
(size.ounces). Inside the enum, a bare property name reads it on this.
# An enum can declare computed properties after its cases, read like
# fields: `size.ounces`, not `size.ounces()`.
enum Size {
Small,
Medium,
Large,
var ounces: int {
get {
return match (this) {
Small => 8,
Medium => 12,
Large => 16,
};
}
}
# Inside the enum, a bare property name reads it on `this`.
var cups: int {
get { return ounces / 8; }
}
}
# The same works for an enum whose cases carry values.
enum Shape {
Circle(radius: int),
Square(side: int),
var area: int {
get {
return match (this) {
Circle(r) => 3 * r * r,
Square(s) => s * s,
};
}
}
}
main(): int {
var size = Size.Medium;
assert(size.ounces == 12 && Size.Large.cups == 2, "value enum properties");
assert(Shape.Square(4).area == 16 && Shape.Circle(2).area == 12, "properties that read the payload");
return 0;
}
An enum value has nowhere to keep anything and never changes, so an enum’s
property has a get with a body that does not use field, and no set:
main.pep:6:9: error: property 'n' of enum 'E' must be computed: give 'get' a body that does not use 'field'
main.pep:6:38: error: property 'n' of enum 'E' cannot have a 'set' accessor: enum values are immutable
Built-in methods
Every enum has these methods without declaring them, and cannot declare methods of the same names:
| Method | What it does |
|---|---|
value.name() | Returns the case’s name as written ("Red"), a String. Every enum has it. |
Name.from_value(n) | Returns a Result<Name>: Ok with the case whose value is n, or an Err when no case has it. n has the enum’s representation type. Only enums whose cases carry nothing have it. |
from_value is the checked way back from an integer (from a file, a C
library or the network) to a case, since as only goes the other way.
match
match (value) { ... } runs the arm for the case that value holds:
# 'match' runs the arm of the case a value holds, and must handle every case.
enum Light {
Red,
Amber,
Green,
}
next(light: Light): Light {
match (light) {
Red => { return Light.Green; }
Amber => { return Light.Red; }
Green => { return Light.Amber; }
}
return light;
}
may_go(light: Light): bool {
match (light) {
Green => { return true; }
else => { return false; }
}
return false;
}
wait_seconds(light: Light): int {
match (light) {
Red, Amber => { return 30; }
Light.Green => { return 0; }
}
return 0;
}
main(): int {
var light = Light.Red;
var changes = 0;
while (!may_go(light)) {
light = next(light);
changes += 1;
}
assert(changes == 1, "red turns green");
assert(wait_seconds(Light.Amber) == 30, "one arm handles two cases");
assert(wait_seconds(light) == 0, "a pattern may name the enum");
return 0;
}
- Each arm is one or more patterns, separated by commas, then
=>and a block. A pattern is a case name (Red) or the case qualified by its enum (Light.Red). - A pattern for a case that carries values can bind them:
Rect(w, h)declareswandhin its arm, holding the case’s values in order. The bindings areconst._skips a value. A pattern lists all of its case’s values or, written without parentheses (Rect), none. Each value can also be a pattern of its own (see Nested patterns). - Patterns that share an arm must bind the same names, with the same types
(
Rect(w, _), Square(w)). - An
else => { ... }arm, last, handles every case the other arms do not. - The arms must handle every case. A missing case is an error that names
it, located at
match; the language server offers a quick fix that adds an empty arm for each missing case. Anelsethat can never run, because the other arms handle every case, is a warning. - Handling a case twice is an error.
- In a
matchstatement,break,continueandreturninside an arm act on the enclosing loop or function, as they would inside anif.
The value is evaluated once, before any arm runs. A match on a null value
of an enum whose cases carry values panics with match on a null 'Name'; a
value enum is never null.
match expressions, is, and literal patterns
# `match` produces a value, `is` tests one case, and a `match` on an
# integer, character, `bool` or `String` compares with literals.
enum Shape {
Circle(radius: int),
Rect(width: int, height: int),
Point,
}
enum List {
Empty,
Cons(head: int, tail: List),
}
area(s: Shape): int {
return match (s) {
Circle(r) => 3 * r * r,
Rect(w, h) => w * h,
Point => 0,
};
}
keyword(word: String): int {
return match (word) {
"import" => 1,
"export", "extern" => 2,
else => 0,
};
}
# The arms' values are numbers of different types (`0` is an `int`,
# `length` a `ulong`), so the `match` has their common type, `ulong`, and
# converts to the `int` this function returns.
length_of(word: String): int {
return match (word) {
"" => 0,
else => word.length,
};
}
main(): int {
var total = area(Shape.Rect(2, 3)) + keyword("extern");
# `is` binds only in the block of the `if` or `while` it is the
# condition of.
var s = Shape.Circle(1);
if (s is Circle(r)) {
total += r;
}
var list = List.Cons(1, List.Cons(2, List.Empty));
while (list is Cons(head, tail)) {
total += head;
list = tail;
}
var point = Shape.Point is Point;
if (point && !(s is Rect)) {
total += 100;
}
# A `match` on an integer needs an `else`: literals cannot list every
# value.
var size = match (total) {
0 => "none",
1, 2, 3 => "few",
else => "many",
};
assert(size == "many", "total is past three");
assert(length_of("four") == 4 && length_of("") == 0, "lengths convert");
assert(total == 6 + 2 + 1 + 3 + 100, "every step adds up");
return 0;
}
- A
matchthat produces a value can go wherever a value can: aftervar x =,return, in an argument. Each arm ispatterns => value, and arms are separated by commas (one after the last is allowed). It follows the rules above: a pattern binds names for its arm’s value, and the arms must handle every case unless there is anelse. The value has the first arm’s type, and every other arm’s value must convert to it. When the arms are numbers of different types, the value has their common type, as C’s? :does (intandlonggivelong), and converts from it wherever it goes (0or aulonglength, returned as anint). A closure in an arm’s value cannot use a name the arm binds; use amatchstatement there. value is Patternis abool: whethervalueholds that case (d is Line,!(d is Text)). It takes any one pattern. When it is the whole condition of aniforwhile, its bindings are declared in that block only:if (d is Text(t)) { ... }readstinside the braces, andwhile (list is Cons(head, tail)) { ... }examineslistagain on every iteration. Anywhere else,iscannot bind names.isis a keyword only between a value and a pattern, so it stays usable as a name (token.is(text)).- A trait value is matched by type rather than by case:
Circle(c)tests for the structCircleand binds the value as one. See Converting a trait value back. - Literal patterns: a
matchon an integer, a character, aboolor aStringcompares with literals (1,-1,'a',true,"import"), using==. Literals cannot list every value, so such amatchneeds anelsearm, except on aboolthat handles bothtrueandfalse. Matching the same literal twice is an error. A match on an enum takes its case names, not literals, and a match oninttakes literals, not names (except constants).
Nested patterns
The values in a case’s parentheses are patterns, as a tuple pattern’s elements are, so patterns nest to any depth:
# Patterns nest: the values a case carries can be matched by patterns of
# their own, as tuple elements can.
enum Doc {
Text(text: String),
Line,
Pair(left: Doc, right: Doc),
}
describe(found: Optional<Doc>): String {
return match (found) {
Empty => "nothing",
# A literal compares the value: only an empty `Text`.
Value(Text("")) => "empty text",
# A case of the value's enum, binding its own values.
Value(Text(t)) => t,
Value(Line) => "a line",
# Deeper still, with `_` for a value that does not matter.
Value(Pair(Line, _)) => "a pair that starts with a line",
Value(Pair) => "a pair",
};
}
main(): int {
assert(describe(Optional<Doc>.Empty) == "nothing", "no document");
assert(describe(Optional<Doc>.Value(Doc.Text(""))) == "empty text", "a literal value");
assert(describe(Optional<Doc>.Value(Doc.Text("hi"))) == "hi", "a bound value");
assert(
describe(Optional<Doc>.Value(Doc.Pair(Doc.Line, Doc.Text("x")))) == "a pair that starts with a line",
"two levels deep"
);
# A bare name binds the value, unless the value's enum has a case of
# that name: here `Empty` is `Optional<int>`'s case.
var nested = Optional<Optional<int>>.Value(Optional<int>.Empty);
var depth = match (nested) {
Empty => 0,
Value(Empty) => 1,
Value(Value(n)) => 2 + n,
};
assert(depth == 1, "the inner Optional is empty");
# Ranges and guards work inside too, and so does `is`.
var count = Optional<int>.Value(7);
var size = match (count) {
Value(0) => "none",
Value(1..10) => "a few",
Value(n) if n < 0 => "negative",
Value(_) => "many",
Empty => "unknown",
};
assert(size == "a few", "a range inside a case");
var pair = Doc.Pair(Doc.Text("left"), Doc.Line);
if (pair is Pair(Text(word), Line)) {
assert(word == "left", "is binds nested names");
}
return 0;
}
- Each value takes
_, a name, a case of its enum (with patterns of its own values:Value(Pair(Line, _))), a literal, a range or constant, or a tuple pattern. - A bare name binds the value, unless the value’s enum has a case of that
name, which it then matches (
Value(Empty)), or it names a module-scope constant. A name repeated in one pattern (Pair(x, x)) must be a case. - A nested pattern handles part of its case: the arms must still handle
every value. A case that only nested patterns handle is reported with the
value they miss (
match on 'Optional<Doc>' does not handle 'Value(Line)'), and a pattern whose values the earlier arms already match is an error. - Patterns that share an arm bind the same names, each at the same place:
Pair(Text(t), Line), Pair(Text(t), Pair)bindstfrom either, butPair(Text(t), _), Pair(_, Text(t))is an error. - A nested test reads a value only once the case around it matched, so the tests stop at the first part that does not match.
Ranges and constants
A match on an integer or a character can compare with a range of
values, and a match on an integer, a character or a bool with a
module-scope constant:
# Range and constant patterns: a `match` on an integer or a character
# compares with ranges (`1..9`, `'a'..>'z'`) and module-scope constants.
const LIMIT = 100;
size(n: int): String {
return match (n) {
0 => "none",
# `1..10` stops before 10; `10..>99` includes 99.
1..10 => "a few",
10..>99 => "many",
LIMIT => "the limit",
else => "out of range",
};
}
kind(c: char): String {
return match (c) {
'a'..>'z', 'A'..>'Z' => "letter",
'0'..>'9' => "digit",
else => "other",
};
}
# Ranges that cover every value of the type need no `else`.
half(b: u8): int {
return match (b) {
0..128 => 0,
128..>255 => 1,
};
}
main(): int {
assert(size(0) == "none" && size(9) == "a few" && size(10) == "many", "ranges");
assert(size(100) == "the limit" && size(-1) == "out of range", "a constant, then else");
assert(kind('q') == "letter" && kind('7') == "digit" && kind('!') == "other", "character ranges");
u8 high = 200;
assert(half(3) == 0 && half(high) == 1, "every u8 is handled");
# In a tuple pattern, a range or a constant compares its element.
var pair = (LIMIT, true);
var which = match (pair) {
(0..LIMIT, _) => 1,
(LIMIT, true) => 2,
(_, _) => 3,
};
assert(which == 2, "the constant matches the first element");
return 0;
}
start..endmatches fromstartup to but not includingend, andstart..>endincludesend, as ranges do elsewhere. Each bound is a literal (-1,'z') or a constant (MIN..MAX). Ranges match integers and characters only, notboolorString.- A name that is a module-scope constant (
const LIMIT = 100;, declared in the file or imported, orlimits.MAXthrough a module) compares the value with it, like a literal. A local variable is not a pattern; compare with it in a guard instead (n if n == limit). - Inside a tuple pattern, a range or a
constant compares its element (
(0..LIMIT, _)), and any other bare name binds it, as before. - When every bound is a literal, the compiler knows which values a pattern
matches: a pattern whose values the earlier arms already match is an
error (
2..>5after1..9), and amatchwhose ranges and literals cover every value of its type (0..128and128..>255for au8) needs noelse. A pattern with a constant bound covers nothing for this check. - A
matchon an enum takes its cases, not ranges or constants, even a constant holding one of its cases.
Guards
An arm can add a guard after its patterns, if condition: the arm runs
only when one of its patterns matches and the condition holds. Otherwise
the next arm is tried, as if this one had not matched.
# A guard (`if condition`) after an arm's patterns: the arm runs only when a
# pattern matches and the guard holds; otherwise the next arm is tried.
enum Shape {
Circle(radius: int),
Rect(width: int, height: int),
Point,
}
describe(s: Shape): String {
return match (s) {
# The guard reads the names its patterns bind.
Circle(r) if r > 10 => "a large circle",
Circle => "a circle",
Rect(w, h) if w == h => "a square",
Rect => "a rectangle",
Point => "a point",
};
}
# A guarded arm handles nothing for the check that every value is handled,
# since its guard may fail: the `bool` match below still needs `false`.
toll(heavy: bool, axles: int): int {
match (heavy) {
true if axles > 4 => {
return 30;
}
true => {
return 20;
}
false => {
return 10;
}
}
}
main(): int {
assert(describe(Shape.Circle(20)) == "a large circle", "the guard holds");
assert(describe(Shape.Circle(2)) == "a circle", "the guard fails, the next arm runs");
assert(describe(Shape.Rect(3, 3)) == "a square", "equal sides");
assert(describe(Shape.Rect(3, 4)) == "a rectangle", "unequal sides");
assert(toll(true, 6) + toll(true, 2) + toll(false, 2) == 60, "each arm runs once");
# Guards work on tuples too.
var pair = (5, true);
var kind = match (pair) {
(n, true) if n > 3 => 1,
(_, true) => 2,
(_, false) => 3,
};
assert(kind == 1, "the first arm's guard holds");
return 0;
}
- The guard reads the names the arm’s patterns bind (
Circle(r) if r > 10). With several patterns (Word(s), Quoted(s) if s.length == 0), it applies to the arm, whichever pattern matched. - Guards work in
matchstatements andmatchexpressions, over enums, literals and tuples.istakes no guard: writex is P && condition, or test the condition inside theif. - A guarded arm handles nothing when the compiler checks that every case is
handled, since its guard may fail: a
matchwhose only arm forCirclehas a guard does not handleCircle, and a later unguarded arm (orelse) must. Anelseafter guarded arms is not unreachable. - A guarded arm for a case an earlier unguarded arm already handles is an error, like any case handled twice. Two guarded arms may name the same case.
- A closure in a guard cannot use a name the arm binds.
Current limits
- An enum whose cases carry nothing cannot be generic, by design: its value
is a plain integer, so a type parameter would have nothing to describe.
An enum whose cases carry values can be (
enum List<T>). - An enum whose cases carry values compares by identity with
==, as a struct does, unless it is a[Value]enum, is[Equatable], or declares==(with a matchinghash<T>to be a key). - A constant is a pattern only when it is declared at module scope.
- A
matchcannot examine floating-point values. - The methods of an enum whose cases carry nothing have no value form:
c.namewithout the call is an error (“‘name’ is a method of ‘Color’: call it as ‘name()’”), where a struct’sp.normis a bound method. Wrap the call in a closure instead,() => c.name(). - An enum whose cases carry nothing is extended only with a trait
(
extend Color with Describe { ... }); declare its other methods inside it. It is never a trait value, since a trait value is a reference to an object, which such an enum’s value is not.