enum Types
An enum defines a tagged union type: a value that is exactly one of several
named variants, optionally carrying a payload.
Use enums to model:
- finite state machines (connection state, parser state),
- protocol messages and events,
- and any API where “exactly one of these cases” is the core invariant.
If your goal is “a function can fail with one of several error shapes”, prefer typed errors (typed errors) over enums.
Notes#
What works end-to-end today (parser → checker → lowering → codegen):
- Enum declarations with:
- unit variants (
A), - tuple variants (
Data(int)andPair(int, int)), - and an optional trailing comma after the last variant.
- Construction:
- unit variants as values:
E::A, - tuple variants as calls:
E::Data(7). - type-directed shorthand:
- when the expected type is an enum
E, unit variants may be written asA(sugar forE::A), - when the expected type is an enum
E, tuple variants may be written asData(7)(sugar forE::Data(7)). - Function signatures:
- enums may be used in parameter lists and return types,
- error-producing functions may return enums (
-> E | ErrorType...), andcall()?works when the success type is an enum. matchexpression over enums:- patterns are restricted to enum variants (
E::A,E::Data(x)) and may use shorthand (A,Data(x)) when the scrutinee type is the enum, - binders may be names or
_, - no guards (
if ...) yet, - and expression-form matches must still be exhaustive in the current subset:
- either by explicit arm coverage for every variant,
- or by a single final wildcard
_arm that covers all remaining unmatched variants. ??over ordinary two-variant enums:- for a named enum with exactly two declared variants,
value ?? fallbacktreats the first declared variant as the “success” arm, - if that first variant is unit, the expression yields that enum value,
- if that first variant carries exactly one payload, the expression yields that payload,
- and if the enum value is the second declared variant, the fallback expression is evaluated.
matchstatement over ordinary enum values:- enum variant arms must use the qualified form
E::Variant(...), - bare identifiers remain reserved for binder-style arms,
- and both expression-form and statement-form ordinary enum matches now allow
one final
_catch-all arm, which must cover at least one remaining variant. - Generic enums (monomorphized):
enum Name(T, ...) { ... }declarations are supported in module-set builds that run monomorphization,- instantiated enums behave like ordinary enums once referenced (including
construction and
match), - callers typically introduce a local type alias for the instantiated enum
(for example
type R = Result(int, string);) and then useR::Ok(...)/R::Err(...)as constructors and patterns. implblocks on enums:- enums may have
implblocks (including genericimpl EnumName(T, ...)), - static methods are callable as
EnumName.method(...)(including through type aliases for instantiated generic enums), - instance methods are callable as
value.method(...)when the first parameter is a receiver (self: &EnumName/mut self: &EnumName), - the special
constructormethod used bynew Type(...)is forstructtypes; enums do not supportconstructormethods in the Supported forms.
Not implemented yet (or not yet stable/documented):
- Guards in enum match arms (
E::A if cond => ...). - A stable ABI story for passing/returning enums across the C99 boundary (do not assume an enum layout until it is specified in abi libsilk).
When the compiler rejects an enum construct in the Supported forms, the most
common error is E2002 (“unsupported expression in the Supported forms”). Type
mismatches inside enum constructors or match arms are E2001 (“type mismatch”).
Surface Syntax#
Enum declarations introduce a nominal type and its variants:
enum RecvJob {
Msg(Job),
Cancelled,
Timeout,
}
Rules:
- Variant names are identifiers and must be unique within the enum.
- Variant names may not be the reserved optional constructors
Some/None. - An enum must declare at least one variant.
- A variant is either:
- a unit variant (no payload):
Cancelled, - or a tuple variant with one or more payload element types:
Msg(Job),Pair(int, int). - A trailing comma after the last variant is permitted.
Construction#
Unit variants#
Unit variants are constructed as values using Enum::Variant (or, in
type-directed contexts, just Variant):
enum E {
A,
B,
}
fn main () -> int {
let x: E = E::A;
let y: E = A;
return 0;
}
Notes:
E::A()andA()are invalid in the Supported forms (unit variants are not callable).- A unit variant is an ordinary value of its enum type. It may initialize a
binding or aggregate field, be returned or passed to a function, and be
assigned to an existing mutable binding or struct field. These value uses
accept both the qualified
E::Aform and the type-directedAshorthand.
Two-Variant ?? Shortcut#
When a named enum declares exactly two variants, the ?? operator may be used
as a compact success/fallback form.
Rules in the Supported forms:
- declaration order matters: the first declared variant is the “success” case, and the second declared variant triggers the fallback,
- if the first variant is unit,
value ?? fallbackyields that enum value, - if the first variant carries exactly one payload,
value ?? fallbackyields that payload, - if the first variant carries more than one payload element, use
matchinstead, - the second variant may be unit or may carry payloads; its payload is ignored
by the
??form and the fallback expression runs. - the right-hand side is still an expression in the current grammar; terminal
statements such as
return,break, andcontinueare not accepted there yet.
Examples:
enum Ready {
Go,
Stop,
}
fn require_ready (r: Ready) -> Ready {
return r ?? Ready::Go;
}
enum ParsedPort {
Port(int),
Invalid,
}
fn port_or_default (p: ParsedPort) -> int {
return p ?? 80;
}
Tuple variants#
Tuple variants are constructed using Enum::Variant(<args...>) where the
argument count and types match the variant’s declared payload element types:
enum E {
Data(int),
Pair(int, int),
Empty,
}
fn main () -> int {
let a: E = E::Data(7);
let b: E = Data(7);
let b: E = E::Pair(1, 2);
let c: E = E::Empty;
return 0;
}
Notes:
E::Databy itself is not a value in the Supported forms (tuple variants must be constructed with(...)).- If a tuple-variant constructor argument has the wrong type, you get
E2001. - If the argument count does not match the variant definition, the compiler
currently rejects the construct with
E2002.
Generic enums (instantiation via alias)#
When an enum is generic, callers typically alias an instantiation and then use that alias as the qualifier for constructors:
enum Result(T, E) {
Ok(T),
Err(E),
}
type R = Result(int, int);
fn main () -> int {
let x: R = R::Ok(123);
return match x {
R::Ok(v) => v,
R::Err(_) => 0,
};
}
Namespaced enums (packages)#
Across packages, enums and variants may be referenced with :: qualification.
For example, if util defines enum Mode { Inc, Dec }, an importer can write:
util::Modeas the type name, andutil::Mode::Inc/util::Mode::Decas the constructors and patterns.
See packages imports exports for module-set rules and for how package imports seed qualified type names.
Matching#
Enum values are typically consumed via match expressions. The match
expression rules are defined in flow match; this section
focuses on the enum-specific subset.
Patterns#
Enum patterns are variant patterns:
- Unit variant:
E::A - Tuple variant:
E::Data(x),E::Pair(a, b) - Tuple binder omission:
E::Pair(_, b)(underscore binder ignores that element)
Shorthand:
- When the scrutinee type is the enum
E, the qualifier may be omitted: - Unit variant:
A - Tuple variant:
Data(x),Pair(a, b)
For instantiated generic enums, the qualifier E may be a type alias (for
example type R = Result(int, string); then R::Ok(v) / R::Err(e)).
Binders:
- introduce a name scoped to that arm only, and
- shadow outer bindings of the same name (because they create a new binding in the arm’s environment).
Exhaustiveness#
In the Supported forms, enum exhaustiveness is split by match form:
- Expression form:
- either there is exactly one arm per enum variant,
- or a single final wildcard arm (
_ => ...) covers the remaining unmatched variants, - explicit variant arms must not repeat the same variant,
- and if
_is used, it may appear at most once and must be the final arm. - Statement form over ordinary enum values:
- either there is exactly one arm per enum variant,
- or a single final wildcard arm (
_ => ...) covers the remaining unmatched variants, - explicit variant arms must not repeat the same variant,
- and if
_is used, it may appear at most once and must be the final arm.
If a match is not exhaustive:
- expression form currently reports
E2002, and - statement form without full coverage uses the checker’s missing-arm path when it reaches that analysis.
Example: unit enum match#
enum E {
A,
B,
}
fn main () -> int {
let v: E = E::A;
let x: int = match v {
E::A => 10,
E::B => 20,
};
if x != 10 {
return 1;
}
return 0;
}
Example: unit enum match with final wildcard#
enum State {
Ready,
Busy,
Closed,
}
fn score (s: State) -> int {
return match s {
Ready => 10,
_ => 20,
};
}
Example: tuple enum match (payload binders)#
enum E {
Pair(int, int),
Empty,
}
fn main () -> int {
let v: E = E::Pair(1, 2);
let x: int = match v {
E::Pair(a, _) => a,
E::Empty => 0,
};
if x != 1 {
return 1;
}
return 0;
}
Example: struct payload enum match#
struct Job {
id: int,
}
enum RecvJob {
Msg(Job),
Cancelled,
}
fn main () -> int {
let j: Job = Job{ id: 5 };
let evt: RecvJob = RecvJob::Msg(j);
let rc: int = match evt {
RecvJob::Msg(job) => job.id,
RecvJob::Cancelled => 0,
};
if rc != 5 {
return 1;
}
return 0;
}
Representation#
Enums are values. In the current IR-backed lowering, an enum value is lowered to scalar slots as:
- a
u64tag (variant index in declaration order, starting at0), and - a payload region that includes a distinct slot range for each variant’s payload elements, in variant declaration order.
Conceptually:
(u64 tag,
payload slots for variant 0,
payload slots for variant 1,
...)
Only the active variant’s payload region is meaningful for a given value; other payload regions are unspecified.
This representation is an implementation detail and is expected to evolve (for example, toward a tag + max-payload “union-style” layout) as the compiler and ABI mature.
Common Pitfalls#
- Forgetting parentheses:
E::Data(7)is valid, butE::Datais not a value in the Supported forms (errorE2002). - Calling a unit variant:
E::Ais a value;E::A()is rejected (E2002). - Wrong binder count:
E::Pair(a)does not matchPair(int, int)(E2002). - Non-exhaustive matches: you must list every variant (error
E2002in the Supported forms). - Assuming enum equality is defined: use
matchto inspect the tag/payload; the backend does not define==/!=over enums yet.
Related Documents#
- flow match (match expression rules)
- structs impls layout (struct payloads)
- types (nominal types and type annotations)
- packages imports exports (namespaces and imports)
- typed errors (typed errors, not enums)
Source repository · Edit this page · View Markdown