Enums
An enum is a nominal value type whose members give names to a defined set of underlying values. Sec enums may use integer or string representations, may participate in the typed error system, may be generic, and may also describe exact-width hardware encodings.
enum Direction {
North
East
South
West
}
Enum members are values of the enum type itself. They are not untyped constants and do not implicitly become their underlying integer or string representation.
bit-backed hardware enums are open over every bit
pattern representable by their declared width.
Core Model
Sec has two semantic enum domains:
| Enum kind | Domain | Typical purpose |
|---|---|---|
| Ordinary enum | Closed over declared underlying value classes | Application states, identifiers, protocols, errors |
| Bit-backed enum | Open over every value representable by bit[N] |
Hardware register fields and raw device encodings |
This distinction is semantic. It does not depend merely on the machine representation chosen by the backend.
An ordinary enum backed by uint8 is still closed. A
bit[8] enum is open even if both eventually occupy eight bits.
Declaration Forms
The ordinary form is:
enum Color {
Red
Green
Blue
}
The equivalent explicit type-declaration form is also valid:
type Color enum {
Red
Green
Blue
}
An enum must contain at least one member.
Members may be separated by line breaks or commas where the grammar permits it, and a trailing comma is allowed:
enum Direction {
North,
East,
South,
West,
}
Underlying Types
An ordinary enum without an explicit underlying type uses int.
enum Status {
Unknown
Active
Disabled
}
is an ordinary int-backed enum.
Explicit integer representation
enum HttpStatus uint16 {
Ok = 200
NotFound = 404
}
An ordinary enum may use any valid integer type as its underlying representation. The selected type is part of the enum declaration and constrains the values representable by its member initializers.
enum SmallState uint8 {
Off = 0
On = 1
}
enum LargeCode uint128 {
First = 1
Second = 2
}
String representation
enum Program string {
OneCare = "Zebra OneCare"
VIQ = "Z1C+VIQ"
AdvancedExchange = "Advanced Exchange"
}
A string-backed enum remains a nominal enum. Its members are values of
Program, not ordinary string values.
Hardware bit representation
enum DeviceMode bit[2] {
Off = 0b00
Active = 0b01
Sleep = 0b10
}
bit is shorthand for bit[1] in enum underlying
position. Bit-backed enum widths are language-defined from 1 through 256 bits.
Members and Namespace
Enum members are named typed constants in the enum's namespace.
enum Direction {
North
East
South
West
}
let direction: Direction := Direction.North
Member names must be unique within one enum. Different enum types may reuse the same member names.
enum Direction {
North
South
}
enum Pole {
North
South
}
let direction := Direction.North
let pole := Pole.North
An enum member has the nominal enum type, not its underlying representation:
let value: int := Direction.North // invalid implicit conversion
Convert explicitly when the underlying value is required.
Integer Member Initializers
An explicit enum member initializer uses =:
enum HttpStatus int {
Ok = 200
NotFound = 404
}
The initializer must be a compile-time integer constant expression representable by the enum's underlying integer type.
This is not canonical initializer syntax:
enum Status int {
Ready: 1
}
Use:
enum Status int {
Ready = 1
}
Integer-backed enums have an additional compile-time mechanism,
iota, for concise value sequences.
iota
iota is a compile-time integer constant available only while
evaluating integer-backed enum member initializers.
Its value is the zero-based declaration position of the current member.
enum Direction {
North
East
South
West
}
is equivalent to beginning the sequence with:
enum Direction {
North = iota
East
South
West
}
and resolves to:
North = 0
East = 1
South = 2
West = 3
Explicit numeric values do not reset iota. Each enum has its
own independent iota sequence.
iota is not a general language constant and is not available
in string-backed enums.
Initializer Repetition
After the first integer-backed member, omitting an initializer repeats the
preceding initializer expression and evaluates that expression using the
current member's iota.
enum Code int {
A = iota // 0
B // 1
C // 2
D = 10 // 10
E // 10
}
E becomes 10 because the expression
10 is repeated. Sec does not interpret omission as
"previous resolved value plus one".
To jump and continue a sequence from the new position, express the jump
in terms of iota:
enum Code int {
A = iota // 0
B // 1
C // 2
D = iota + 7 // 10
E // 11
F // 12
}
Bit-mask style sequences
enum Permission uint {
None = 0
Read = 1 << iota
Write
Execute
}
resolves to:
None = 0
Read = 2
Write = 4
Execute = 8
Every repeated expression is checked again for overflow and representability
after substituting the current iota.
String-Backed Enums
A string-backed enum uses string as its explicit underlying type.
enum Program string {
OneCare = "Zebra OneCare"
VIQ = "Z1C+VIQ"
AdvancedExchange = "Advanced Exchange"
}
Every member must have an explicit compile-time string initializer.
This is invalid:
enum Program string {
OneCare = "Zebra OneCare"
VIQ
}
String enums have no implicit sequence and no initializer-repetition rule.
iota is not available.
String-backed enums are closed ordinary enums. Only the declared underlying string value classes are valid enum values.
let program := Program("Zebra OneCare")
This constant conversion is valid because the string names a declared value class.
let program := Program("Unknown")
This is a compile-time error when no member declares "Unknown".
Runtime conversion from string is checked:
let program := try Program(input)
Failure reports EnumValueError.UndeclaredValue.
Conversion back to the underlying string is explicit and infallible:
let text := string(Program.OneCare)
Value Aliases
More than one enum member name may have the same underlying value.
enum ResultCode int {
Success = 0
Ok = 0
NotFound = 404
Missing = 404
}
Duplicate underlying values are valid aliases. The member names remain distinct declared names, but values with the same underlying representation belong to one runtime value class.
The same applies to string-backed enums:
enum Program string {
OneCare = "Zebra OneCare"
LegacyOneCare = "Zebra OneCare"
}
Aliases compare equal:
if ResultCode.Success == ResultCode.Ok {
...
}
They also represent one coverage class for exhaustive match.
Matching one alias covers that underlying runtime value.
Enum Defaults
Every valid non-empty enum is defaultable.
Implicit default
Unless another member is explicitly marked default, the first
declared member is the enum's semantic default.
enum Status int {
Unknown = 10
Active = 20
}
The default is:
Status.Unknown
Numeric zero has no special priority.
Explicit default member
enum ConnectionState {
Connecting
Connected
Disconnected default
}
Exactly one member may be marked default.
The marker appears after the member name and before an optional initializer:
enum ConnectionState int {
Connecting = 10
Connected = 20
Disconnected default = 30
}
The default marker changes default selection only.
It does not alter the initializer expression, iota, repetition
rules or alias semantics.
Default initialization
let mut state: ConnectionState
initializes state to the enum's semantic default.
This is not uninitialized storage.
The ordinary immutable-binding rule still requires an initializer:
let state: ConnectionState // invalid
Ordinary Enums Are Closed
An ordinary enum's semantic domain contains only the unique underlying values declared by its members.
enum Color int {
Red = 1
Green = 2
Blue = 3
}
Valid Color values are the declared value classes:
1
2
3
The fact that int can represent many other values does not make
those values valid Color values.
The same closed-domain rule applies to string-backed enums.
Error Enums
A closed enum may be declared as a concrete Sec error type by adding
the error marker.
enum IOError error {
OpenError
ReadError
WriteError
}
When an explicit enum representation is needed, the error
marker follows the ordinary representation information:
enum ProtocolError uint16 error {
InvalidFrame = 1
UnsupportedVersion = 2
}
The enum remains a closed nominal enum. Its additional semantic property is
that values of the enum are assignable to the compiler-known root
error type.
fn Read() Result[Data, IOError] {
return Err(IOError.ReadError)
}
A concrete enum error may widen to error:
fn ReadOpen() Result[Data, error] {
return Err(IOError.ReadError)
}
Widening preserves the concrete error type and member identity.
The reverse conversion is not implicit:
IOError -> error // valid widening
error -> IOError // not implicit
IOError -> ParseError // not implicit
Declaring an enum as an error type does not introduce general inheritance, exceptions or a mandatory error-object layout.
Error variants that need payload data belong to a union error rather than an enum error:
type DetailedError union error {
OpenError {
Path: string
Code: int
}
ReadError {
Path: string
Offset: uint64
}
}
Generic Enums
Enums may declare ordinary Sec generic type parameters.
enum SortOrder[T] {
Ascending
Descending
}
Every complete concrete argument list produces a distinct nominal enum type:
SortOrder[User]
SortOrder[Product]
These types remain distinct even when the generic parameter contributes no runtime storage and the physical representation is identical.
Once concretized, ordinary enum rules apply to members, defaults, conversions,
equality, switch, match and representation.
Generic enum parameters may participate in type identity and associated behavior without becoming runtime generic parameters.
Enum members remain payload-less. If each alternative needs to carry different data, use a union.
Nested Enums
An enum may be declared as an associated nested type inside an owning
impl.
impl Vehicle {
enum FuelType {
Petrol
Diesel
Electric
}
}
Outside the implementation, use the qualified type and member names:
Vehicle.FuelType
Vehicle.FuelType.Diesel
Inside impl Vehicle, the shorter nested name may be used when
normal scope resolution makes it unambiguous:
FuelType
FuelType.Diesel
Behavior and Associated Values
An enum is a nominal type and may have ordinary Sec behavior through
impl.
enum Status {
Ready
Waiting
Failed
}
impl Status {
fn IsTerminal() bool {
return self == Status.Failed
}
let DefaultFailure: Status := Status.Failed
}
Instance methods receive implicit self. Immutable associated values
declared directly with let are type-associated members:
let failure := Status.DefaultFailure
Associated values are not enum members. They do not alter the enum's underlying
value domain, member initialization, aliases, iota, default-member
selection or exhaustive match coverage.
An implementation may also provide static methods, properties and other
implementation members permitted by the general impl rules.
impl cannot add new enum members to an already declared enum.
Bit-Backed Hardware Enums
A hardware enum may use bit or bit[N] as its
underlying representation.
enum AlertPinMode bit[2] {
AlertDisabled = 0b00
Comparator = 0b01
Interrupt = 0b10
}
Unlike ordinary enums, a bit-backed enum is open over the complete domain representable by its bit width.
For bit[2], all four bit patterns are valid:
0b00
0b01
0b10
0b11
The first three have declared names in the example. 0b11 is
still a valid AlertPinMode value even though it is unnamed.
This is necessary because hardware can expose reserved, undocumented, device-specific or future encodings. Sec preserves those values rather than trapping or inventing a member name.
bit[N] enum does not need
to enumerate all 2^N possible values.
Unknown values in match
match mode {
AlertPinMode.AlertDisabled => HandleDisabled()
AlertPinMode.Comparator => HandleComparator()
AlertPinMode.Interrupt => HandleInterrupt()
_ => HandleUnknownMode()
}
When undeclared bit patterns remain possible, listing only named members is not exhaustive.
Bit Enums in Register Fields
A named register field may use a bit-backed enum when the enum width exactly matches the field width.
enum AlertPinMode bit[2] {
AlertDisabled = 0b00
Comparator = 0b01
Interrupt = 0b10
}
type AlertControl register[8] {
Mode: AlertPinMode
_: bit[6]
}
Reading Mode preserves the complete raw two-bit pattern.
If the hardware returns 0b11, the read still produces a valid
AlertPinMode value.
Ordinary closed enums are not interchangeable with bit-backed register-field types merely because their numeric representation could fit.
Conversions
Enums do not implicitly convert to or from their underlying representation. Different enum types do not implicitly convert to each other.
Enum to integer
let raw := int(Color.Red)
Normal checked integer conversion rules apply to the target integer type.
Enum to string
let raw := string(Program.OneCare)
For a valid string-backed enum, conversion to string is infallible.
Constant integer to closed enum
enum Color int {
Red = 1
Green = 2
Blue = 3
}
let color := Color(2)
This is valid because 2 is a declared Color value class.
let color := Color(9)
This is a compile-time error because 9 is not a declared
value of the closed enum.
Runtime integer to closed enum
let color := try Color(raw)
When the compiler cannot prove that the runtime integer names a valid enum value, the conversion is fallible.
Possible failures are:
EnumValueError.UndeclaredValue
EnumValueError.OutOfRange
OutOfRange means the value cannot be represented by the enum's
underlying representation. UndeclaredValue means the value fits
the representation but does not belong to the closed enum domain.
Integer to open bit enum
enum DeviceMode bit[2] {
Off = 0b00
Active = 0b01
Sleep = 0b10
}
let mode := DeviceMode(0b11)
This is valid because 0b11 fits the two-bit domain.
It is a valid unnamed value.
A runtime conversion requires try only when width fit cannot be proven:
let mode := try DeviceMode(raw)
An in-range unnamed bit pattern never fails with
EnumValueError.UndeclaredValue.
String to string-backed enum
let program := try Program(input)
Runtime conversion checks whether the input equals one of the declared
string value classes. Failure is EnumValueError.UndeclaredValue.
Enum to enum
Cross-enum conversion is never implicit.
Integer-backed enums convert explicitly through a suitable integer representation:
let raw := int(SourceMode.Active)
let target := try TargetMode(raw)
String-backed enums use an explicit string intermediary.
Assignment, Equality and Arithmetic
Values of the same enum type use ordinary assignment rules:
let mut state := Status.Ready
state = Status.Waiting
No implicit assignment exists between:
- an enum and its underlying integer or string type;
- two different enum types.
Equality
if color == Color.Red {
HandleRed()
}
Equality and inequality are defined between values of the same enum type. Comparison uses the semantic underlying enum value.
Aliases with the same underlying value compare equal.
No direct arithmetic or ordering
Enum values do not directly support arithmetic:
let next := status + 1 // invalid
They are not directly ordered:
if status < Status.Failed { // invalid
}
String-backed enums likewise do not acquire string concatenation or ordering.
Convert explicitly to the underlying representation when numeric or textual operations are genuinely intended.
Match and Exhaustiveness
match understands enum domains directly.
Closed ordinary enum
enum Direction {
North
East
South
West
}
match direction {
Direction.North => MoveNorth()
Direction.East => MoveEast()
Direction.South => MoveSouth()
Direction.West => MoveWest()
}
This is exhaustive because every unique declared runtime value class is covered.
Numeric or string aliases count as one coverage class.
Open bit-backed enum
match mode {
DeviceMode.Off => HandleOff()
DeviceMode.Active => HandleActive()
DeviceMode.Sleep => HandleSleep()
_ => HandleUnknown()
}
Exhaustiveness for bit[N] is based on all 2^N
bit patterns, not merely on declared names.
A catch-all is unnecessary only when explicit patterns already cover the complete bit domain.
Switch
An enum may be used as a normal switch subject.
switch status {
case Status.Ready:
Start()
case Status.Waiting:
Wait()
default:
Stop()
}
switch uses normal value-comparison semantics.
It does not become exhaustive merely because the subject is an enum.
Use match when exhaustive enum handling is part of the intended contract.
Foreign C Enums
A C ABI enum declared through the foreign interface is a separate category from both ordinary Sec enums and bit-backed hardware enums.
extern "C" type NativeStatus enum {
...
}
Its representation is selected by the active C ABI.
Its value domain is open over every raw value representable by that ABI enum representation, because foreign code may return values not named by the Sec declaration.
Code that needs exhaustive interpretation of a foreign enum therefore requires an unknown-value fallback where undeclared raw values remain possible.
bit[N] enum, and foreign C ABI enum have different
domain rules even when their machine representations happen to be similar.
Diagnostics and Tooling
Important enum diagnostics include:
- duplicate enum member name;
- invalid enum underlying type;
- empty enum declaration;
- initializer is not a constant expression;
- initializer is not representable by the underlying type;
- string-backed member requires an explicit initializer;
- string-backed initializer is not a compile-time string constant;
iotaused in a string-backed enum;iotaused outside enum member initialization;- multiple members marked
default; - underlying value is not a declared member of a closed enum;
- integer value does not fit a bit-backed enum width;
- fallible enum conversion requires
try; - non-exhaustive match for a closed enum;
- non-exhaustive match for an open bit-backed enum.
Tooling should expose both the underlying representation and the enum-domain kind:
closed ordinary enum
open bit-backed enum
For open hardware enums, diagnostics and hover information should make clear that undeclared in-range bit patterns are valid runtime values.
Summary
- An enum is a distinct nominal value type.
- Enum members are typed enum constants in the enum namespace.
- An ordinary enum defaults to underlying type
int. - Ordinary enums may use explicit integer or
stringrepresentations. - Integer-backed member initializers are compile-time integer expressions.
iotais the zero-based member position for integer-backed enum initialization.- The first omitted integer initializer is implicit
iota. - Later omitted integer initializers repeat the preceding initializer expression.
- An explicit numeric jump does not reset
iota. - String-backed members require explicit compile-time string values.
iotadoes not apply to string-backed enums.- Duplicate underlying values are valid aliases.
- Aliases with the same underlying value compare equal and form one match-coverage class.
- Every valid non-empty enum is defaultable.
- The first member is the default unless exactly one member is marked
default. - Raw numeric zero has no special default status.
- Ordinary integer- and string-backed enums are closed over declared value classes.
- An ordinary closed enum may be marked as a concrete Sec
errortype. - Concrete error enums widen to
errorwhile preserving concrete identity. - Generic enums are supported and every concrete argument list creates a distinct nominal type.
- Generic enum members remain payload-less; payload-bearing alternatives belong to unions.
- Enums may be nested inside an owning
impl. - Enums may have methods, properties and associated values through ordinary
implrules. - Associated values do not add enum members or alter the enum value domain.
bitandbit[N]enums are open over every representable bit pattern.- Undeclared in-range hardware bit patterns are valid unnamed enum values.
- A bit-backed enum may type a register field when widths match exactly.
- Enums do not implicitly convert to or from their underlying representation.
- Runtime conversion into a closed enum is checked and requires
trywhen failure cannot be ruled out. - Runtime conversion into a bit-backed enum fails only when the value does not fit the bit width.
- Enum equality is defined within the same enum type.
- Direct arithmetic, ordering and string concatenation are not enum operations.
matchuses the enum's semantic domain for exhaustiveness.switchmay branch on enum values but is not enum-exhaustive.- Foreign C enums form a separate open-domain ABI category.