Enums
Enums define a type with a fixed set of named variants.
Defining an Enum
enum Color { Red, Green, Blue }Each variant is automatically assigned an integer value starting from 0:
Red= 0Green= 1Blue= 2
Explicit values and integer decoding
Enums without payloads can assign integer literals to variants:
enum Kind { Data = 21, Next, Heartbeat = 7, Unknown = -1 }The default representation is i32. Choose a different integer type after
the enum name to control its size and signedness:
enum Color u8 {
RED = 1,
GREEN = 2,
BLUE = 3
}Color occupies one byte, including when stored in arrays and class fields.
The supported types are i8, u8, i16, u16, i32, u32, i64, and
u64. The representation remains an implementation detail of the enum's
storage: Color is still a distinct type from u8.
An omitted value is zero for the first variant, or one greater than the
preceding variant (Next is 22 above). Negative and hexadecimal literals are
supported. Values must be unique and fit in the chosen integer type; an
automatic increment past its maximum is an error. Unsigned enums reject
negative values. Both explicit values and custom integer types require an
enum without payloads.
Read a value with _convert<i32>(Kind.Data). To decode an integer, use
_enum_from_int<Kind>(value), which returns std.Option<Kind> and requires
the standard library:
using std;
/* Decode a protocol identifier, using zero for an unknown identifier. */
function read_kind(value: u8) i32 {
return match _enum_from_int<Kind>(value) {
Option.Some(kind) => match kind {
Kind.Data => 1,
Kind.Next => 2,
Kind.Heartbeat => 3,
Kind.Unknown => 4
},
Option.None => 0
};
}Decoding compares the original integer before narrowing, so an oversized
integer cannot wrap into a valid variant. _convert<Kind>(value) is not
allowed.
Using Enums
Variables
Declare variables with an enum type and access variants using dot notation:
var c: Color = Color.Green;An enum declared in a module can be named through its module path, the same
way a module's classes and functions can, or by its bare name after a using:
using std;
var mode = std.io.FileMode.Write; // through the module path
var file = std.io.File();
file.open("notes.txt", mode);Comparison
Enum values can be compared with == and !=:
function is_red(c: Color) bool {
return c == Color.Red;
}
function main() i32 {
var c: Color = Color.Blue;
if (c != Color.Red) {
return 1;
}
return 0;
}Enums as Function Parameters
Enums can be passed to and returned from functions:
enum Direction { Up, Down, Left, Right }
function opposite(d: Direction) Direction {
if (d == Direction.Up) { return Direction.Down; }
if (d == Direction.Down) { return Direction.Up; }
if (d == Direction.Left) { return Direction.Right; }
return Direction.Left;
}
function main() i32 {
var dir: Direction = Direction.Up;
var opp: Direction = opposite(dir);
if (opp == Direction.Down) {
return 1;
}
return 0;
}Multiple Enums
You can define multiple enums in the same file:
enum Status { Pending, Running, Completed }
enum Priority { Low, Medium, High }
function main() i32 {
var s: Status = Status.Running;
var p: Priority = Priority.High;
return 0;
}Enums in Classes
Enums can be used as field types in classes:
enum TaskStatus { Pending, InProgress, Done }
class Task {
var status: TaskStatus;
init() {
this.status = TaskStatus.Pending;
}
method start() void {
this.status = TaskStatus.InProgress;
}
method complete() void {
this.status = TaskStatus.Done;
}
method is_done() bool {
return this.status == TaskStatus.Done;
}
}Pattern Matching with Enums
Combine enums with match expressions for clean branching:
enum Color { Red, Green, Blue }
function to_rgb(c: Color) i32 {
return match c {
Color.Red => 0xFF0000,
Color.Green => 0x00FF00,
Color.Blue => 0x0000FF,
_ => 0
};
}See Match Expressions for more details.
Payload-Carrying Variants
Variants can carry data, turning an enum into a tagged union:
enum Shape {
Circle(f64), // radius
Rect(f64, f64), // width, height
Empty
}
var c = Shape.Circle(2.5);
var r = Shape.Rect(3.0, 4.0);The only way to reach a payload is a match with a destructuring pattern,
which binds each payload position to a fresh immutable local (use _ to skip
a position):
function area(s: ref Shape) f64 {
return match s {
Shape.Circle(r) => 3.14 * r * r,
Shape.Rect(w, h) => w * h,
Shape.Empty => 0.0
};
}Matches on enums are checked for exhaustiveness: every variant must be
covered, or a _ arm must be present. The error names the missing variants.
Rules for payload enums:
- They are value types with the same ownership rules as classes: pass by
refto borrow, pass by value to move, returned by value (moved to the caller). ==/!=are not defined on them —matchis the eliminator.- Payload types may be primitives, pointers, other enums, interfaces, and
classes — including classes that own heap memory (
String,Vec<T>, anything withdeinit). Reference payloads borrow their referents and cannot outlive them. Arrays and recursive payloads without indirection are rejected. - Payload-free enums are unchanged: plain
i32values with==comparison, usable inextern "C"signatures. Payload enums cannot cross the C boundary yet.
Ownership and Drops
Sun never implicitly copies a compound value, and payload enums follow that rule throughout:
- Construction moves.
Holder.Hold(owner)movesownerinto the enum; usingownerafterwards is a use-after-move error. - Assignment moves and drops.
var b = a;andb = a;movea(it cannot be used again). Overwriting a variable or field that already holds a payload drops the old payload first. - Drops are automatic. When an enum whose payload owns resources goes out
of scope (block end, loop iteration,
return,break/continue, or an exception unwinding through the frame), its live payload is dropped exactly once — the compiler synthesizes a per-enum drop routine that switches on the tag. - Moving a payload consumes its enum. If any reachable arm takes ownership
of a compound payload, the match consumes the owned enum and gives its
compound payloads to the selected arm's bindings. Bindings can move into a
result, another variable, or a by-value argument. Remaining payloads, including
_positions, are dropped when the arm exits. The original enum cannot be used again. - Inspection borrows automatically. If no reachable arm takes ownership of a payload, the bindings borrow in place and the enum remains available. Matching an explicit reference always borrows and rejects payload moves. Scalar payloads are read by value.
var maybe = Option.Some(String(alloc, "hello"));
var text = match maybe {
Option.Some(s) => s,
Option.None => String(alloc, "fallback")
};
// text now owns the string; maybe has been consumed.To inspect without consuming:
function length(maybe: const ref Option<String>) i64 {
return match maybe {
Option.Some(s) => s.length(),
Option.None => 0
};
}Generic Enums
Enums can take type parameters, making reusable shapes like options and results expressible:
enum Option<T> { Some(T), None }
// _Result<T, E> is builtin; It is independent of the standard library.
function find(x: i32) Option<i32> {
if (x > 0) { return Option.Some(x * 2); } // T inferred from the argument
return Option.None; // T from the return type
}
var a = Option.Some(21); // Option<i32>, inferred
var b: Option<f64> = Option.None; // type arguments from the annotationType arguments are inferred from payload arguments where possible; a bare
unit variant like Option.None takes them from the expected type (a variable
annotation, the function return type, or an assigned field), and it is an
error when no context determines them. Each specialization is a distinct
type — Option<i32> and Option<f64> coexist independently — and matches
use the generic name in patterns:
match a {
Option.Some(v) => v,
Option.None => 0
}Not yet supported: explicit type arguments in expression position
(Option<i32>.Some(5) — use inference or an annotation), nested patterns,
and arrays or globals of payload enums. These are planned follow-ups.
Trailing Commas
Trailing commas are allowed in enum definitions:
enum Days {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}