Enums

An enum is a top-level list of named values, one member per line:

enum Color do
    RED
    GREEN
    BLUE
end

Color current = Color.GREEN
print(current == Color.GREEN, current) // true GREEN

Members are written as Enum.MEMBER. Enum values can be stored in variables, fields, and arrays, passed to and returned from functions, and compared with == and !=. Values of different enums are distinct types, and enums do not support arithmetic or ordering. print shows the member name.

Positions and values

Each member has a position, from 0 in the order written, and a value, which is its position unless one is written:

enum Status do
    OK = 200
    MOVED = 301
    MISSING = 404
end
Expression Result
ordinal(member) its position, an int
int(member) its value; i64(member) and the other integer conversions too
Kind.from_ordinal(i) the member at position i, as an Option[Kind]: NONE when there is none
len(Kind) the number of members

An array can be indexed by a member, which stands for its position, so a table with a value per member is an array:

enum Kind do
    STONE
    WOOD
    METAL
end

int[] points = [5, 10, 25]
print(points[Kind.WOOD])              // 10
for i = 0 to len(Kind) - 1 do
    match Kind.from_ordinal(i) do
        when SOME(kind) then
            print(kind, points[kind])
        when NONE then
    end
end

A written value is an integer that fits an i32, as C's enums do, and no two members may have the same value. Members that carry values cannot have numbers.

Flags

An enum marked #[flags] is a set of flags: its members' values are 1, 2, 4, and so on, unless written, and |, &, ^, and ~ combine them into values of the same enum. flag in flags says whether flags holds all of flag's bits, and print shows the members a value holds, joined with |:

#[flags]
enum Permission do
    READ
    WRITE
    EXEC
    ALL = 7
end

Permission p = Permission.READ | Permission.WRITE
print(p, Permission.WRITE in p, Permission.EXEC in p)   // READ|WRITE true false

Members that carry values

A member can carry values, declared like function parameters. Such a member is written with its values in parentheses; a member without values is written as before:

enum Shape do
    CIRCLE(f64 radius)
    RECT(f64 width, f64 height)
    EMPTY
end

Shape[] shapes = [Shape.CIRCLE(1.0), Shape.RECT(2.0, 3.0), Shape.EMPTY]

A value of such an enum is always exactly one member with its values, so a Shape is never null. The values are read with match, which gives them names, and which must handle every member:

func area(Shape s) f64 do
    match s do
        when CIRCLE(r) then
            return 3.14159 * r * r
        when RECT(w, h) then
            return w * h
        when EMPTY then
            return 0.0
    end
end

Leaving out EMPTY there is an error, Match does not handle Shape.EMPTY, and so is every match that misses a member added later: the compiler finds each place that must change. That is what such an enum is for. Other languages call it a tagged union, a sum type, or, in Rust and Swift, an enum too.

An interface also gives one type to values of several kinds, but it leaves the kinds open: any type with its methods is one, in any module, and code calls the methods without knowing which kind it has. An enum with values closes them: its members are all the kinds there are, in one place, and code looks at each. Use an enum when the kinds are fixed and the code that uses them decides what each means, as the tokens of a parser or the results of a request; use an interface when new kinds come from elsewhere, as shapes a program's users add.

A member may carry the enum itself, which describes trees and lists:

enum Expr do
    NUMBER(int value)
    ADD(Expr left, Expr right)
end

These values are references, like structs: copying one copies the reference. print and to_str show the member name, and == is not available for them; use match.

Type parameters

An enum can take type parameters in brackets after its name. Its members use them like types, and each use of the enum gives them types:

enum Tree[T] do
    LEAF(T value)
    NODE(Tree[T] left, Tree[T] right)
end

Tree[str] tree = Tree.NODE(Tree.LEAF("a"), Tree.LEAF("b"))

Tree[str] and Tree[int] are different types. A value such as Tree.LEAF("a") takes its type arguments from where it goes, such as the declared type of a variable, parameter, field, array, or function result, and otherwise from the values it carries. When neither tells, as in print(Option.NONE) on its own, the compiler asks for a declared type. Structs take type parameters the same way, and so do functions, as generic functions.