Structs and Enums
This chapter defines nominal struct and enum declarations, their value construction, and enum pattern binding behavior.
Structs and Value Construction
Struct values may be constructed with explicit named-field struct literals when the structural entry is accessible.
pub struct User {
pub id: u64
name: String
}
let user = User {
id: 1,
name: String.copy("alice"),
}
Rules:
- Struct literal syntax is
Type { field: value, ... }. - A struct may declare zero fields. Its structural literal is
Type {}under the same visibility and construction-surface rules as any other struct. - Struct literal fields are comma-delimited and may use one trailing comma on any layout.
- The type in a struct literal must name a struct type. A generic owner may use complete explicit type arguments or infer all of them under Generic Owner Arguments.
- Every field must be initialized exactly once.
- Field order in the literal is free.
- Unknown fields are compile errors.
- Duplicate fields are compile errors.
- Field initializer expressions are evaluated left to right in the order written in the literal.
- Field initializer expressions follow normal ownership, move, copy, borrow, and postfix
?rules. - If a later field initializer fails through postfix
?, already initialized owned field values are dropped in reverse initialization order before the failure propagates. - Private fields may be initialized only in their authored source and sources that directly see it.
- Public fields may be initialized from other modules.
- Construction entries cannot be overloaded.
- Field default values, struct update syntax, positional structs, and tuple structs are not supported.
- A
constructdeclaration groups type-owned construction functions and typed literals. Each member has its own visibility. The declaration does not change structural construction visibility. See Construction Surfaces. - Names such as
new,init, andcreateare ordinary construction-function names when declared insideconstruct. The compiler does not special-case them.
When initialization logic or validation is needed, place a public construction function in the type's construct declaration.
construct User {
pub func create(id: u64, name: String): Self {
return User {
id: id,
name: move name,
}
}
}
Outside the private field's direct source-access boundary, a struct with private fields can be created only through public APIs exposed by its module.
let user = User.create(1, String.copy("alice"))
Enums and Variant Construction
Enums represent finite variants and may carry data.
enum AppError {
missing_path
open_failed(path: &str)
}
Rules:
- An enum must declare at least one variant. A zero-variant enum is invalid and does not define a nominal uninhabited value type.
- Enum variant names use snake_case.
- Variants may carry zero or more payload values.
- Variant payload declarations and constructor arguments are comma-delimited and may use one trailing comma on any layout.
- Payloadless variants are constructed as
EnumName.variant_name. - Payload variants are constructed as
EnumName.variant_name(args...). - Every enum uses a
u8ABI tag and must declare between 1 and 256 variants, inclusive, whether or not its variants carry payloads. - Variant construction requires the payload arity and types to match the variant declaration.
- Variant payload arguments are evaluated left to right.
- Variant constructors are qualified with the enum name, such as
AppError.open_failed(path). - Variant constructors must be qualified.
- A generic enum owner may use complete explicit type arguments or infer all of them under Generic Owner Arguments.
- Variant constructors are not ordinary functions and are not magic identifier names; they are generated by the enum declaration.
- Enum variants and construction functions share the type member namespace. Defining a construction function with the same member name as a variant is a compile error.
- If an enum is public, its variants are public.
- Per-variant visibility is not supported.
Examples:
let state = ScanState.inside_word
let error = AppError.open_failed(path)
match is the control-flow form for enum pattern matching.
match error {
AppError.missing_path {
...
}
AppError.open_failed(path) {
...
}
_ {
...
}
}
Enum patterns are shallow and positional. Their source form is centralized under Control Expressions and Enum Patterns.
The payload list uses the common comma-delimited-list grammar. A payloadless variant uses the first form. A payload-bearing variant uses the second form and supplies exactly one slot for every declared payload field.
enum Pair {
values(left: String, right: String)
}
match &pair {
Pair.values(_, right) {
inspect(right) // right: &String
}
}
The pattern target chooses how payload names are bound. Borrowed matching inspects an enum without extracting its payload:
var message = next_message()
match &message {
Message.text(text) {
inspect(text) // text: &String
}
_ {
...
}
}
match &+message {
Message.text(text) {
text.clear() // text: &+String
}
_ {
...
}
}
match move message {
Message.text(text) {
consume(move text) // text: String
}
_ {
...
}
}
Rules:
- Match arms use
Pattern { ... }. - A variant pattern must use the exact enum qualifier and variant name selected by the target enum type.
- Payload pattern slots are positional and their count must equal the variant payload arity.
- An identifier slot introduces one branch-local binding for the payload field at that position. It does not need to repeat the field's declaration name.
_always occupies exactly one payload position and introduces no binding. Ignoring every field of a multi-payload variant requires one_for each field, such asPair.values(_, _).Pair.values(_)is therefore an arity error whenvalueshas two payload fields._never abbreviates an entire payload list.- Nested patterns, literal patterns, binding modifiers, field-name patterns, and rest patterns are not supported.
- Enum variant patterns are tag patterns, not value refinements. Payload binding names and
_control projection only. - Every enum payload field may use any sized type that is valid as a struct field. Construction, local storage, arguments, returns, assignment, optional/fallible wrapping, and pattern matching apply recursively to payload aggregates without a separate runtime type allowlist.
- A pattern target whose type is
Enum,&Enum, or&+Enumselects one of the binding modes in the table below. Pattern matching dereferences a borrowed target only for tag inspection and payload projection; it does not introduce a general implicit dereference conversion.
| Pattern target | Payload name type | Effect on the target |
|---|---|---|
| New owned enum temporary | declared payload type | Consumes the temporary |
Existing enum place without move | declared payload type, only when that payload is copyable | Copies the named payload; retains the enum place |
Readonly borrow expression of type &Enum | &Payload | Retains the enum and creates a readonly payload borrow |
Readwrite borrow expression of type &+Enum | &+Payload | Retains the enum and creates an exclusive readwrite payload borrow |
move place | declared payload type | Consumes the enum place |
- The borrowed modes apply both to an explicit target such as
match &valueormatch &+valueand to any target expression already typed as&Enumor&+Enum. Using an existing borrow as a pattern target is a use of that borrow, not an ownership transfer. - Every payload name in one pattern uses the target's binding mode. A borrowed target therefore
binds even a copyable payload as
&Payloador&+Payload; it never performs a hidden payload copy. Code that needs owned copies matches an existing enum place without a borrow. - Creating an
&+Enumtarget follows the ordinary writable-place and exclusivity rules. Payload borrows derived from a borrowed target carry the target borrow's provenance and keep that borrow active through their last source-level use. A payload borrow may be returned or stored only when the ordinary borrow and provenance rules permit it. - An existing enum place without
movemay bind copyable payloads even when another variant makes the enum type move-only. Naming a move-only payload from that target is an error. A payloadless pattern or_payload may still inspect the tag without consuming the place. - Binding owned move-only payloads from an existing local, parameter, or named struct field
requires an explicit
movetarget, such asmatch move resultormatch move holder.result. The ordinary move-place restrictions still apply; indexes, dereferences, and computed projections are not move sources. - A newly produced owned enum temporary, including a call result, variant constructor, control
expression result, or value produced through postfix
?, postfix!,catch, orotherwise, is already owned by the pattern operation and does not usemove. - An owned target is consumed as a whole into pattern-operation temporary storage before arm execution. Named payload bindings assume their fields' drop obligations. Unnamed fields and the complete active payload in a fallback arm remain in that storage. Its residual initialized fields are dropped by the ordinary statement-temporary rules after the selected arm is evaluated, or by early-exit cleanup if the arm exits the statement. No field of a consumed enum is dropped twice.
- If an owned enum has a type-owned drop declaration and an explicit arm transfers any named move-only payload, the drop body observes the complete enum exactly once after tag selection and before any payload leaves pattern-operation storage. The residual cleanup later drops only unnamed initialized payload fields and does not call the type-owned drop body again.
- If such an arm binds only copyable payloads, those bindings copy their values and the complete enum remains in pattern-operation storage. Its ordinary residual cleanup invokes the type-owned drop body and then drops the active payload. Fallback and implicit non-match paths likewise keep the complete enum until ordinary cleanup.
- Enum cleanup reads the active tag and drops only initialized fields of that variant. Fields drop in reverse payload declaration order and recursively use the same struct, enum, fixed-array, and outcome cleanup rules. Fixed-array elements drop in reverse index order.
- Scope-end cleanup, parameter cleanup, explicit discard initializers, call-result temporaries, assignment replacement, and partial control-flow cleanup all use the same active-variant rule.
- Payload names in a pattern are bound only inside that arm block.
_inside a payload pattern, such asAppError.open_failed(_), occupies exactly one declared payload position without introducing a binding. For a consumed target, that unnamed owned field remains under pattern-operation temporary cleanup. For a borrowed or retained target, the field is neither copied nor moved._by itself is valid only as thematchfallback arm. It is not a validif ispattern.
Selection order, fallback placement, exhaustiveness, result typing, termination, and single evaluation of the target belong to Control Flow.
Example value selection:
return match error {
AppError.missing_path { missing_code() }
AppError.open_failed(path) { code_for(path) }
_ { unknown_code() }
}
if enum_expr is Pattern checks one enum pattern.
if error is AppError.open_failed(path) {
report(path)
} else if error is AppError.read_failed(path) {
report(path)
} else {
report_other(error)
}
Rules:
if enum_expr is Patternuses the same enum pattern syntax asmatch.ifpattern targets use the same owned, copied, readonly-borrowed, readwrite-borrowed, and moved binding modes asmatch.- Payload names are bound only inside the then body.
if enum_expr is Enum.variant(_)checks only the variant of a one-payload enum case and ignores that payload without introducing a binding. A multi-payload variant requires one slot per field.- Payload names are not available in
elseor laterelse ifbranches. if isdoes not apply to fallible valuesT!.if isdoes not apply to optional valuesT?.