#Data Types and Enums
Status: language specification draft.
This chapter defines structs and enums.
- data.kind.structA struct is a nominal product type with value semantics.
- data.kind.enumAn enum is a nominal sum type.
- data.kind.no-classNeither is a class, and neither creates an inheritance hierarchy.
struct Point:
x: i32
y: i32
enum Shape:
Dot
Circle(radius: f64)#Struct Declarations
A struct declaration defines a struct with named, typed fields. A struct
value is copied, never shared.
struct Vec2:
x: f64
y: f64 = 0.0
fn demo() -> f64:
let mut a = Vec2 { x: 1.0 }
b := a # b is a copy of a
a.x = 5.0 # changes a only
b.x # 1.0- data.struct.decl
struct Name:declares a struct. Its body holds named fields, defaults, visibility, decorators, and generic parameters. - data.decl.nominalStruct identity is nominal: two declarations with the same fields introduce different types.
- data.struct.valueA struct is a value type, as Value Types defines.
- data.struct.receiver-formsA method of a struct takes
selformut self, its own read-only or changeable copy of the receiver, or&self,&mut self,*self, or*mut self, a reference to the caller's value. - data.struct.mut-selfA struct method's
mut selfchanges its copy only. The caller's value is unchanged unless the method returns the copy and the caller keeps it. - data.struct.embedA struct may embed a struct, by the rules of Data Embedding for names, limits, and member promotion. An embedded field is a value field, held inline.
- data.struct.embed.plainA struct literal fills an embedded field as it fills a named field, as in
Post { Timestamps: ts }, andpost.Timestamps = tsassigns it. Both copy the value, as every struct copy does. - data.struct.no-recursionA struct that contains itself by value is an error. Error:
recursive-value-type. - data.struct.containsA value contains the values of its struct fields, enum payloads, tuple elements, optional payload, and newtype base, and what those contain.
- data.struct.contains.endsA collection, a trait value, or a function value holds a reference, so containment ends there.
- data.struct.contains.referenceA counted reference,
*Tor*mut T, also ends containment. - data.struct.no-recursion.fixThe
recursive-value-typeerror of a struct has a fix-it that writes*before the type of each field that contains the struct.
struct Vec2:
x: f64
y: f64
enum Unit:
Metre
Foot
struct Pair:
Vec2 # valid: an embedded struct
Unit # error: embedded-non-data
weight: f64
struct Chain:
next: Chain? # error: recursive-value-typeimpl Vec2:
fn moved(mut self, dx: f64) -> Vec2:
self.x += dx
selfWhy. A struct holds its fields inline, so one that contained itself would have no finite size. Recursion goes through a reference or a collection, as
next: *Chain?andnext: List[Chain]do.
See also: Value Types, Writable Places.
#Fields
A struct declares named, typed fields:
struct User:
id: string
email: string
display_name: string? = .None- data.field.uniqueField names must be unique within the struct. Error:
duplicate-field. The named parameters of an enum header, and the named payload parameters of one variant, are unique in the same way. - data.field.typedEvery field has an explicit type.
- data.field.no-mut-modifierFields have no standalone
mutmodifier:mut friend: Useris an error. Error:mutable-field-modifier. - data.field.mut-type
friend: *mut Userdeclares a field whose type grants mutable access through that reference. - data.field.embedded-no-mutAn embedded field is written without
mut:BaseembedsBase, andmut Baseis an error. Error:mutable-field-modifier.
struct User:
mut name: string # error: mutable-field-modifier
mut Base # error: mutable-field-modifier
friend: *mut User # valid: the type grants mutable accessWhy. Access to an embedded field already follows its container.
#Field Defaults
- data.default.allowedAn ordinary named field may have a default expression.
- data.default.typeThe default must be assignable to the declared field type.
- data.default.requirement-freeThe default must obey the same requirement-free rule as a function-parameter default.
- data.default.evalA default is evaluated separately for each construction, not when the struct is declared.
- data.default.scopeA default sees the declaration's lexical scope.
- data.default.no-field-bindingA default does not implicitly bind other fields of the new value.
- data.default.embeddedEmbedded fields have no default syntax.
struct Config:
retries: i32 = 3
name: string
fn demo() -> i32:
Config { name: "shop" }.retries # 3: the default fills the missing fieldSee also: Default Values.
#Field Visibility
- data.vis.privateNamed fields are module-private unless individually marked
pub. - data.vis.type-not-fieldsA public struct does not make its unmarked fields public.
- data.vis.embedded-publicAn embedded field takes no marker and is always public: it is visible wherever its outer type is.
- data.vis.literalIn another module, a struct literal may construct the type only when all its fields are public. Any other such literal is an error. Error:
private-member. - data.vis.private-fieldsIn another module, private fields cannot be named, initialized, or carried through a copy-update literal. Doing so is an error. Error:
private-member. - data.vis.factoryA public factory function can construct a value with private fields inside the defining module.
Because an embedded field is public, embedding a module-private struct in a
public struct leaks it, as
module.vis.signature.coverage
states:
struct Timestamps:
created_at: i32
pub struct Post:
Timestamps # error: private-type-leak
title: stringWhy. An embedded field is always public: it is visible wherever its outer type is.
See also: Member Resolution, Public Uses And Visibility.
#Fieldless Data Types
A struct with no fields is written with pass:
struct Unit: pass
unit := Unit {}- data.empty.passAn empty struct uses
passas its body. - data.empty.no-allocationConstructing a value of a fieldless struct allocates nothing.
#Recursive Data Types
- data.recursive.through-referenceA struct may refer to itself through a reference, an optional reference, or a collection, as
next: *Chain?andreplies: List[Comment]do. - data.recursive.mutualModule-level structs may also be mutually recursive through such links.
- data.recursive.localLocal structs follow declaration-point visibility and cannot refer to a later local declaration.
- data.recursive.required-fieldsRecursion does not imply optionality: a program must still provide a value for every required field during construction.
struct Chain:
value: i32
next: *Chain?Note. A recursive graph therefore normally includes an optional, enum, list, or another finite base case.
See also: data.struct.no-recursion,
Generic And Recursive Enums.
#Construction And Access
A typed brace literal constructs a struct value, and value.field accesses
a field.
user := User {
id: "user_123",
email: "ada@example.com",
display_name: "Ada",
}#Data Literals
- data.literal.formStruct values use typed brace literals.
- data.literal.requiredEach field without a default must be initialized exactly once. Omitting one is an error. Error:
missing-required-field. - data.literal.defaultedA field with a default may be omitted or supplied explicitly.
- data.literal.unknownAn unknown field is a compile-time error. Error:
unknown-field. - data.literal.duplicateA duplicate field is a compile-time error. Error:
duplicate-field. - data.literal.any-orderFields may appear in any order.
- data.literal.eval-explicitExplicit field expressions are evaluated in source order.
- data.literal.eval-defaultsThen defaults for omitted fields are evaluated in field declaration order.
missing := User { id: "user_123" } # error: missing-required-field
unknown := User { id: "u", email: "e", nickname: "Ada" } # error: unknown-field
twice := User { id: "u", id: "v", email: "e" } # error: duplicate-field#Field Shorthand
- data.literal.shorthandA field may be written as its bare name when a binding of that name is in scope:
Point { x, y }meansPoint { x: x, y: y }. - data.literal.shorthand.mixedShorthand fields and
field: valuefields may be mixed in one literal, as inPoint { x, y: 0 }. - data.literal.shorthand.rulesA shorthand field follows every rule of the
field: valueit stands for, so it counts towarddata.literal.requiredanddata.literal.duplicate. - data.literal.shorthand.unknownA shorthand name that resolves to no binding in scope is an error, as for any unknown value name. Error:
unknown-name.
struct Point:
x: i32
y: i32
fn place(x: i32, y: i32) -> (Point, Point):
(Point { x, y }, Point { x, y: 0 })
fn broken(x: i32) -> Point:
Point { x, y } # error: unknown-nameWhy. A data pattern already binds
Point { x, y }field by field, so the literal builds with the same spelling it destructures with.
See also: Data Expressions.
#Copy-Update Literals
A copy-update literal builds a new value from an existing one:
renamed := User {
...user,
display_name: "Ada Lovelace",
}- data.update.spreadCopy-update construction uses one leading spread.
- data.update.same-typeThe spread has the same struct type.
- data.update.supplies-allThe spread supplies every field.
- data.update.replaceExplicit fields replace copied values.
- data.update.new-valueA copy-update literal creates a new struct value; it does not mutate the spread source.
- data.update.no-defaultsBecause the spread supplies every field, copy-update does not evaluate defaults for unlisted fields.
- data.update.shorthandAn explicit replacement may use field shorthand, as in
Point { ...origin, x }, which meansPoint { ...origin, x: x }.
See also: Data Embedding, Data Expressions.
#Field Access
- data.access.syntaxField access uses
value.field. - data.access.writable-placeAssigning a field, or changing its value in place, requires a writable place, as Writable Places states.
- data.access.assignmentDirect assignment to a visible field enforces its declared type, not arbitrary validation or cross-field invariants.
- data.access.graphThe language permits shared mutable children through
*mutreferences, and does not guarantee invariants over the entire reachable object graph. - data.access.other-moduleIn another module, field access reaches only public fields; member lookup skips the others.
struct User:
pub name: string
email: string
fn rename(user: &mut User, name: string) -> void:
user.name = name # a write through a &mut referenceNote. Keep fields private and expose controlled methods when writes to those fields must preserve such invariants. This does not control other mutable references to a value that a private field refers to.
See also: Writable Places, Data Embedding, Member Resolution.
#Data Patterns
- data.pattern.formStruct values may be destructured in
matchpatterns using the sameStructName { ... }form. - data.pattern.subsetThe pattern may mention any subset of visible fields; omitted fields are not tested.
- data.pattern.fieldsWithin the braces,
fieldbinds the field value andfield: patternapplies a nested pattern.
struct Point:
x: i32
y: i32
fn on_axis(p: Point) -> bool:
match p:
Point { x: 0 } => true
Point { y: 0 } => true
_ => falseSee also: Match Expressions.
#Data Embedding
A bare type-name member is an embedded field, which embeds another struct inline.
struct Post:
Timestamps
id: string
title: stringA struct literal fills the embedded field as it fills a named field:
post := Post {
Timestamps: Timestamps {
created_at: 1700000000,
updated_at: 1700000000,
},
id: "post_123",
title: "Hello",
}#Embedded Field Names
- data.embed.memberA bare type-name member embeds another struct, possibly with type arguments. The embedded field's name, and its key in a struct literal, is the type's final name without type arguments:
Box[T]is the fieldBox. - data.struct.embed.struct-onlyA struct's embedded field must name a struct, a generic struct such as
Box[T], or a transparent alias that resolves to one. Embedding any other type is an error. That covers an enum, a newtype, a trait value type,Any, a builtin or collection type, a function type, and a type parameter. Error:embedded-non-data. - data.embed.uniqueThe name must be unique among the outer struct's fields. A duplicate name that involves an embedded field is an error. Error:
duplicate-embedded-field.
struct Storage:
Box[i32]
Box[string] # error: duplicate-embedded-fieldstruct Holder[T]:
JobStatus # error: embedded-non-data
List[i32] # error: embedded-non-data
T # error: embedded-non-data#Embedding Limits
Embedding is limited in width and depth, and a struct never embeds itself.
| Rule | Limit | Error | Reported on |
|---|---|---|---|
| data.embed.widthWidth | A struct may declare at most three embedded fields. | too-many-embedded-fields |
the fourth embedded field |
| data.embed.depthDepth | Embedding chains may be at most three levels deep: C may embed P1, which embeds P2, which embeds P3. A type that reaches a cycle without being part of it gets no embedding-cycle; its depth counts along chains that never repeat a type. |
embedding-too-deep |
the embedded field that begins the first chain that reaches depth 4 |
| data.embed.depth.selfCycle | A struct that embeds itself, directly or through other types, is an error, in place of embedding-too-deep. |
embedding-cycle |
the embedded field that begins the cycle, in the cycle's first declared type |
struct Post:
Created
Updated
Owned
Tagged # error: too-many-embedded-fields
title: stringHere Site reaches Stamp at depth 4, one level too deep:
struct Audit:
Stamp
by: string
struct Record:
Audit
id: string
struct Page:
Record
title: string
struct Site:
Page # error: embedding-too-deep
host: stringstruct Node:
Edge # error: embedding-cycle
id: string
struct Edge:
Node
weight: i32Note. For the error revamp:
embedding-too-deepis reported at the declaration of every struct that reaches an embedded struct at depth 4. Its message shows the chain, as inC > P1 > P2 > P3 > P4. Theembedding-cyclemessage shows the cycle path, as inNode > Edge > Node.
Note. Together the two limits bound a struct's embedding tree to at most 3 + 9 + 27 = 39 embedded structs.1
#Embedded Field Variance
For variance, an embedded field's type is an invariant position, as Variance states.
struct Box[+T]:
value: T
struct Holder[+T]:
Box[T] # error: invalid-varianceWhy. Access through an embedded field follows the container.
#Member Promotion
- data.promote.membersEmbedding promotes the
pubfields andpubinherent methods of an embedded type for convenient access, as Member Resolution defines.
Each fact about promotion is stated once, in chapter 03 or chapter 09:
| Fact | Rule |
|---|---|
Only the pub fields and pub inherent methods of an embedded struct, at any depth, are promoted; trait methods never are. |
names.promote.member |
An embedded struct's own embedded field is also promoted; it takes no pub marker but is always public. |
names.promote.embedded |
For each name, the shallowest member hides deeper ones, so a pub own member hides a promoted one. |
names.hide.depth |
A private own member with a promoted member's name is ambiguous-promoted-member. |
names.conflict.private-shadow |
Two members with one name at the smallest depth are ambiguous-promoted-member at the declaration. |
names.conflict.error |
| A promoted member means its explicit path, with the embedded field as receiver: there is no overriding. | names.promoted.path |
A trait method of the receiver beside a promoted method is ambiguous-method. |
names.method-lookup.ambiguous |
| Embedding grants no trait conformance. | trait.embed.no-conformance |
| A promoted method never fills a trait method. | trait.impl.fill.never |
struct Left:
pub id: string
struct Right:
pub id: string
struct LeftBox:
Left
struct RightBox:
Right
struct Record:
LeftBox
RightBox # error: ambiguous-promoted-member
struct Base:
pub id: string
struct Entry:
Base
id: string # error: ambiguous-promoted-memberSee also: Member Resolution,
which defines which field x.name or method x.name(args) selects.
#Composition, Not Subtyping
Embedding is composition, not subtyping. The outer struct is a different nominal type, so it is not assignable to the embedded type. A promoted method runs as the embedded type's own method, and embedding grants no trait conformance.
Note. Conformance through an embedded field is written explicitly, with
by, as inimpl Describe for Service by Logger.
See also: Embedding And Trait Satisfaction, Trait Delegation.
#Embedded Field Metadata
An embedded field is a field, so it accepts the same prefix metadata
decorators as a named field, as
annot.target.kind.field
states. Its name is the final type name, and the metadata stays on the
field: promoted members do not carry it.
#Enum Declarations
An enum declaration lists its variants:
enum JobStatus:
Queued
Running
Succeeded
Failed- data.enum.closedAn enum defines a closed set of variants.
- data.enum.uniqueVariant names must be unique within the enum. Error:
duplicate-variant. - data.enum.qualifiedThe qualified form is
JobStatus.Queued. - data.enum.shorthand
.Queuedis also valid where an expected type fixesJobStatus. - data.enum.shorthand.other-enumA matching name in another enum does not create ambiguity when that expected type is known.
- data.enum.shorthand.needs-typeA unique name across the program does not make the shorthand valid without an expected enum type.
- data.enum.unknown-variantA qualified or shorthand variant name that names no variant of the enum is an error. Error:
unknown-variant.
#Variant Payloads
Variants may carry payloads:
enum ToolError:
NotFound(resource: string)
Unauthorized(reason: string)
RateLimited(retry_after_ms: i32)- data.enum.payloadA variant may carry payload parameters.
- data.enum.payload.orderIn a variant constructor call and in a variant pattern, positional arguments and sub-patterns come before
name=ones. Function calls follow the same rule,fn.arg.positional-first. - data.enum.payload.typedEach payload type is explicit.
- data.enum.payload.largeLarge payloads should use a separate struct rather than a nested field block.
- data.enum.payload.no-field-blocksVariant field blocks are not part of the language.
- data.enum.immutableAn enum value never changes once built: its variant and its payload values are fixed at construction.
- data.enum.immutable.shallowThe rule is shallow. A payload of type
*mut Ustill refers to a place, which may change through that reference. - data.enum.no-identityAn enum value has no identity, so every enum type implements
AnyVal, astypes.sealed.anyval-membersstates. - data.enum.no-identity.is
iswith an enum operand is an error. Error:identity-requires-references.
#Variant Construction
A variant is constructed with call syntax:
error := ToolError.NotFound(resource="user_123")- data.enum.construct.callVariant construction follows function-call conventions: positional arguments come before named arguments.
- data.enum.construct.unknownA named argument that names no payload field of the variant is an error. Error:
unknown-field. - data.enum.construct.call-codesOther argument errors use the call codes in Calls, such as
duplicate-argument.
enum Pair:
Value(first: i32, second: i32)
fn make() -> Pair: Pair.Value(missing=1, first=2) # error: unknown-field
fn again() -> Pair: Pair.Value(1, first=2) # error: duplicate-argument#Variant Constructors As Function Values
Some variant constructors are function values:
enum FsError:
NotFound(path: string)
enum SyncError:
Fs(error: FsError)
Retry(attempt: i32, error: FsError)
fn wrap_all(errors: List[FsError], wrap: fn(FsError) -> SyncError) -> List[SyncError]:
[for error in errors => wrap(error)]
wrapped := wrap_all([FsError.NotFound("a.txt")], SyncError.Fs)
retry := fn(error: FsError) -> SyncError: SyncError.Retry(1, error)- data.enum.fn-valueA variant constructor with exactly one payload field is a function value when it is written without an argument clause.
- data.enum.fn-value.type
ToolError.NotFoundhas typefn(string) -> ToolError: one positional parameter of the payload type, the enum as result, no suspension, and the empty requirement row. - data.enum.fn-value.useIt may be passed wherever that function type is expected, such as to a standard-library
map_errinresult.map_err(SyncError.Fs). - data.enum.fn-value.positionalThe payload's field name and positional or named declaration do not matter, because function values take positional arguments only.
- data.enum.fn-value.generic-ruleFor a generic enum, or a variant with its own generic parameters, the value follows the rule for generic function values.
- data.enum.fn-value.generic-argumentPassed as a call argument, it takes its type arguments from the call. So
result.map_err(TaskError.Failed)on aResult[T, FsError]givesTaskError[FsError]. - data.enum.fn-value.generic-unsolvedA generic parameter that remains unsolved is an error. Error:
cannot-infer-type. - data.enum.fn-value.shorthandThe contextual shorthand
.Variantstill needs an expected enum type, so it is never a function value. Using it as one is an error. Error:missing-contextual-enum-type. - data.enum.fn-value.callCalling the value constructs the variant, exactly as calling the constructor does.
- data.enum.fn-value.multipleA variant constructor with two or more payload fields is not a function value and must be called.
- data.enum.fn-value.unsaturatedUsing one without an argument clause is an error. Error:
unsaturated-enum-constructor. - data.enum.fn-value.closureAn explicit closure passes its construction as a function value.
- data.enum.fn-value.payload-freeA payload-free variant, including one whose declaration initializes shared enum data, is an enum value and is selected without
().
fn apply(make: fn(i32, FsError) -> SyncError) -> SyncError:
make(1, FsError.NotFound("a.txt"))
fn retry() -> SyncError:
apply(SyncError.Retry) # error: unsaturated-enum-constructorA generic variant constructor takes its type arguments from the call:
enum FsError:
NotFound(path: string)
enum TaskError[E]:
Failed(error: E)
Cancelled
fn wrap_error[T, E, F](result: Result[T, E], wrap: fn(E) -> F) -> Result[T, F]:
match result:
.Ok(value) => .Ok(value)
.Err(error) => .Err(wrap(error))
fn read(path: string) -> Result[string, FsError]:
.Err(FsError.NotFound(path))
wrapped := wrap_error(read("a.txt"), TaskError.Failed)enum Either[L, R]:
Left(value: L)
Right(value: R)
fn wrap_all[T, F](values: List[T], wrap: fn(T) -> F) -> List[F]:
[for value in values => wrap(value)]
fn main() -> void:
lefts := wrap_all([1, 2], Either.Left) # error: cannot-infer-typeOpen in playgroundIn the second example, nothing determines R. An expected type such as
let lefts: List[Either[i32, string]] would solve it.
See also: Function Types And Values.
#Shared Enum Constructor Data
Enums can declare constructor data that every variant shares:
enum StatusCode(i32):
Ok -> StatusCode(200)
NotFound -> StatusCode(404)
enum HttpStatus(code: i32, phrase: string, retryable: bool = false):
Ok -> HttpStatus(200, phrase="OK")
NotFound -> HttpStatus(404, phrase="Not Found")
ServiceUnavailable -> HttpStatus(503, phrase="Service Unavailable", retryable=true)- data.shared.declareAn enum may declare data shared by all variants.
- data.shared.parametersConstructor parameters may be unnamed or named.
- data.shared.constructorEach variant with shared enum data must provide its enum constructor expression after
->. A variant without one is an error. Error:missing-required-field. - data.shared.ownerThe name after
->must be the enclosing enum. Any other name is an error. Error:variant-result-owner. - data.shared.argumentsThe constructor call follows ordinary positional/named argument ordering and must initialize each shared parameter without a default. Error:
missing-required-field. - data.shared.per-variantShared constructor data belongs to the variant, not to each value: every value of one variant has the same shared data.
- data.shared.module-initEach variant's constructor expression is evaluated once at run time during its declaring module's initialization.
- data.shared.module-init.orderThese evaluations join that module's top-level initialization under
module.init.cycleand Order Inside A Cycle. - data.shared.module-init.empty-rowThe expression must satisfy
module.init.requirement-free.non-script, and it may call any requirement-free function. - data.shared.module-init.effectsIts writes to module state occur during initialization.
- data.shared.module-init.not-lazyThe expression is neither lazy nor compile-time evaluation.
- data.shared.no-payloadThe variant's payload parameters are not in scope in its constructor expression.
- data.shared.not-storedShared data is stored once per variant and never in an enum value, so it adds nothing to a value's size.
- data.shared.generic-copyA generic enum's shared data has one copy per variant of the declaration, not one per instantiation:
Tagged[i32].AandTagged[string].Aread the same copy. - data.shared.type-parameterA shared parameter whose type names a type parameter of the enum is an error, because one copy has no single type. Error:
shared-data-type-parameter.
Why. Shared data describes a variant, like a Java or Kotlin enum constant's constructor arguments. Data that differs from value to value belongs in each variant's payload or in a wrapper struct.
#Shared Parameter Defaults
- data.shared.defaultA shared parameter may declare a default expression.
- data.shared.default.orderAfter the first defaulted parameter, every following shared parameter must also have a default, as with function parameters. A later parameter without one is an error. Error:
default-order. - data.shared.default.empty-rowThe default must satisfy
data.shared.module-init.empty-row, as a function-parameter or struct-field default does. - data.shared.default.eval-onceWhen omitted, the default is evaluated once for the variant during the variant's constructor call at module initialization.
- data.shared.default.eval-orderWithin one variant's constructor call, explicit argument expressions are evaluated first, then omitted defaults in parameter declaration order.
- data.shared.default.scopeA default may refer to earlier named shared parameters but not later ones.
enum Status(code: i32 = 0, phrase: string): # error: default-order
Unknown -> Status(phrase="unknown")#Shared Fields
Shared data is read as fields of the enum value:
code := StatusCode.NotFound._0
phrase := HttpStatus.NotFound.phrase- data.shared.value-readEvery enum value exposes its variant's shared data as fields.
- data.shared.named-fieldA named constructor parameter is available as a field on the enum value.
- data.shared.underscore-fieldAn unnamed parameter uses zero-based tuple-style access, spelled
_0,_1, and so on, as inStatusCode.NotFound._0. - data.shared.read-onlyA shared field is read-only: assigning one is an error, even through a
&mutreference or alet mutbinding. Error:invalid-assignment-target. - data.shared.no-placeA shared field is no place, so a change to its value in place, through a method call or an element store, is an error. Error:
not-writable. - data.shared.payload-fieldsVariant payload fields remain available through pattern matching rather than direct field access, because they do not exist on every variant. A direct access is an error. Error:
unknown-field. - data.shared.payload-namesWithin one variant, a named payload parameter must not duplicate a named shared constructor parameter, nor another named payload parameter of that variant. Error:
duplicate-field. - data.shared.payload-defaultsVariant payload parameters do not have defaults; writing one is an error. Error:
syntax-error.
enum HttpStatus(code: i32, phrase: string):
Ok -> HttpStatus(200, phrase="OK")
Moved(target: string) -> HttpStatus(301, phrase=target) # error: unknown-name
fn rename(status: &mut HttpStatus) -> void:
status.phrase = "Fine" # error: invalid-assignment-target#Generic And Recursive Enums
This section defines generic and recursive enums.
enum Maybe[T]:
Some(value: T)
None
enum Tree[T]:
Leaf(value: T)
Branch(left: *Tree[T], right: *Tree[T])- data.enum.generic.paramsAn enum may declare generic parameters.
- data.enum.generic.instanceEvery variant constructs the enclosing enum instantiated with the declaration's type arguments.
- data.enum.recursiveAn enum may refer to itself through a reference, an optional reference, or a collection, as
Branch(left: *Tree[T], right: *Tree[T])does. - data.enum.no-recursionAn enum that contains itself by value, as
data.struct.containsdefines containment, is an error. Error:recursive-value-type. - data.enum.no-recursion.fixThe error has a fix-it that writes
*before the type of each payload field or shared field that contains the enum. - data.enum.no-recursion.groupStructs and enums that contain one another, each through the next, are one
recursive-value-typeerror, reported at one of them. - data.enum.no-boxingNo value is boxed implicitly: each
*in a recursive enum's types is written, and&on a new value allocates its object.
enum Tree:
Leaf(value: i64)
Node(left: Tree, right: Tree) # error: recursive-value-type
enum Chain:
Link(next: Chain?) # error: recursive-value-type
enum Branch:
Fork(forest: Forest) # error: recursive-value-type
struct Forest:
branch: BranchWhy. An enum holds its payloads inline, as a struct holds its fields, so one that contained itself would have no finite size. Writing the
*keeps every allocation visible, as Go's pointers and Rust'sBoxdo.
Why. hd has no generalized algebraic data types. A typed request and its response pair through a trait with an associated type, and a typed interpreter uses a runtime value enum or a trait.
See also: Variant result types.
#Matching Enums
A match inspects an enum value:
match error:
ToolError.NotFound(resource) => resource
ToolError.Unauthorized(reason) => reason
ToolError.RateLimited(retry_after_ms) => retry_after_ms.to_string()fn queued() -> JobStatus: .Queued
fn label(status: JobStatus) -> string:
match status:
.Queued => "queued"
.Running => "running"
.Succeeded => "succeeded"
.Failed => "failed"- data.match.exhaustiveEnum values are inspected with exhaustive
match. - data.match.variant-patternsVariant patterns may be qualified, or use
.Variantwhen the matched value fixes their enum. - data.match.shorthandThe shorthand also works for payload construction and nested variant patterns when their expected enum type is fixed.
- data.match.no-inferenceThe shorthand is not a way to infer an enum from the variant spelling alone:
status := .Queuedhas no contextual enum type and is an error. Error:missing-contextual-enum-type.
status := .Queued # error: missing-contextual-enum-type#Payload Patterns
Payload patterns destructure a variant's payload:
Expr.Add(l, r)
Expr.Sub(left=l, right=r)
Expr.Scale(value, factor=2)- data.match.payload.parenthesesPayload patterns use parentheses, not braces.
- data.match.payload.namesPositional patterns may bind names different from declared field names.
- data.match.payload.namedNamed patterns use
field=pattern, and no positional pattern may follow a named one. - data.match.payload.constrainLiterals and nested variant patterns may constrain payload values.
See also: Control Flow, which defines pattern scope and exhaustiveness.
#Option And Result
Option[T] and Result[T, E] are prelude enums:
Option[T] |
Result[T, E] |
|
|---|---|---|
| Declaration | enum Option[T]: Some(value: T); None |
enum Result[T, E]: Ok(value: T); Err(error: E) |
| Values and patterns | .Some(value), .None, Option.Some(value), Option.None |
.Ok(value), .Err(error), Result.Ok(value), Result.Err(error) |
| Language support beyond an ordinary enum | the T? spelling; the one-layer implicit wrap of a plain T value where T? is expected; postfix ? |
postfix ?; the Result[void, E] entry-point result |
- data.prelude.optionOptional
T?is sugar for the prelude enumenum Option[T]: Some(value: T); None. - data.prelude.result
Result[T, E]is likewise the prelude enumenum Result[T, E]: Ok(value: T); Err(error: E). - data.prelude.valuesValues of either type are built with the spellings in the table, and matched with the same spellings as patterns.
- data.prelude.supportThe only language support beyond an ordinary enum is what the table lists for each type.
- data.prelude.abiThe representation of either type is an ABI decision, not a source-language difference.
- data.prelude.nullAn implementation may, for example, represent
.Noneas a null reference.
fn head(items: List[i32]) -> i32?:
if items.len() == 0: .None
else: .Some(items[0])See also: Optional Types, Result Types.
#Representation And Garbage Collection
- data.repr.implementationStruct and enum representation is chosen by the implementation subject to observable language semantics.
- data.repr.no-dependencePrograms must not depend on field offsets, variant tags, object addresses, or representation identity unless a future interop facility exposes them explicitly.
- data.repr.gcReachable composite values are garbage collected, and unreachable reference cycles are reclaimable.
- data.repr.no-manualThe language does not expose manual deallocation, user-visible finalizers, or weak references.
- data.repr.runtime-onlyWeak references and finalizers may exist only inside the standard runtime, and user code cannot observe when one clears or runs.
- data.repr.cleanupResource cleanup is separate from memory reclamation.
- data.repr.deferBlock-scoped
deferprovides explicit synchronous cleanup on ordinary control-flow exits and cancellation; it is not an ownership or garbage-collection mechanism.
fn work() -> void: pass
fn done() -> void: pass
fn demo() -> void:
defer: done()
work()Ownership, alias-escape prevention, automatic finalization, and asynchronous or fallible cleanup policy remain deferred in the open-issues umbrella.
#Typed Derivation Of Data And Enums
Structs and enums derive library traits with @derive or a derivation
block, as Typed Derivation defines.
- data.derive.membersFor typed derivation, a struct's members are its fields in declaration order, embedded fields included. An enum's members are each variant's payload parameters.
- data.derive.payload-namesAn unnamed payload parameter is the member
_0,_1, and so on, by position. - data.derive.sharedShared constructor data is not a member. A derivation reads it from the variant's information, and
buildnever reads it. - data.derive.newtypeA newtype derives through its base type, as Derived Newtypes defines.
@derive(Eq, Debug)
struct Point:
x: i32
y: i32
fn same(a: Point, b: Point) -> bool:
a == bSee also: Members And Variants.
#Unsupported Aggregate Extensions
- data.unsupported.layoutStable object layout and component-model representation are ABI concerns and are not observable core-language semantics.
- data.unsupported.non-exhaustiveEnums have no non-exhaustive form, so adding a variant to a public enum is a breaking change for its users.
Note. A library that needs to grow a set of error kinds can wrap a private enum in a struct with a private field. It then exposes accessor methods.
@derive(Eq)
enum Kind:
Timeout
Refused
struct ApiError:
kind: Kind
impl ApiError:
fn is_timeout(self) -> bool:
self.kind == Kind.TimeoutSee also: Variant Payloads, where
data.enum.payload.no-field-blocks rules out variant field blocks.
39 reads as "mi-ku" in Japanese (san-kyuu, 3-9), a nod to Hatsune Miku. The bound was not chosen for this reason, but it is a happy coincidence. ↩︎