Enums

Basic enums

tape
enum Direction : u8 {
    North;      // 0
    East;       // 1
    South;      // 2
    West;       // 3
}

let facing: Direction = Direction.North;

A variant without a value is the previous one plus 1, and the first is 0. A value can be given anywhere and the count goes on from it:

tape
enum Status : i16 {
    Ok;          // 0
    Retry = 10;
    Busy;        // 11
}

When an enum is shared with C, written to a file or sent over the network, write every value, so that adding or reordering a variant can’t change the numbers the other side relies on.

Every enum is its own type

An enum value goes only where its own enum is declared. Its integer is how it is stored, not its type, so converting to or from an integer is written as:

tape
enum Color : u8 { Red = 0; Green = 1; }

let d: Direction = Direction.East;
let n: u8 = d as u8;                  // 1
let back: Direction = n as Direction;  // Direction.East
let next: Direction = ((d as i64 + 1) % 4) as Direction;

let bad1: Direction = Color.Green;     // error: 'bad1' is Direction, given Color
let bad2: Direction = 1;               // error: an integer becomes an enum only with 'as'
let bad3: i64 = d;                     // error: an enum becomes an integer only with 'as'
if (d == Color.Green) { }              // error: cannot compare 'Direction' with 'Color'
let bad4 = d + 1;                      // error: arithmetic on enum 'Direction'
  • Values of one enum compare with ==, != and, by their values, <, <=, >, >=. An enum never compares with an integer or with another enum.
  • as converts an enum to any integer type and an integer to an enum. One enum to another goes through an integer: (c as i64) as Direction.
  • as keeps a value no variant has. For an integer read from a file, a protocol or the runtime, use E.from_value(n) instead (below).
  • The same holds for enum fields, parameters, returns, consts, ?E and the error of T or E. Two enums with the same name in different modules are different types; one enum reached through two imports is one type.

Enums with descriptions

tape
enum HttpStatus : u16 {
    Ok = 200: "success";
    NotFound = 404: "not found";
    ServerError = 500: "internal server error";
}

The description string is accessible at runtime:

tape
let msg: []const u8 = HttpStatus.desc(HttpStatus.NotFound);
// "not found"

Checked conversion: from_value

E.from_value(n) gives the variant whose value is n, or null when there is none. It takes any integer type and compares by value, so a number outside the backing type’s range is null rather than wrapped:

tape
fn parse_dir(raw: u8) -> Direction or ParseErr {
    let d = Direction.from_value(raw);   // ?Direction
    if (d == null) { return ParseErr.BadInput; }
    return d!;
}

Direction.from_value(2)    // Direction.South
Direction.from_value(9)    // null

It works on an imported enum too: geo.Direction.from_value(n).

Backing type

The : u8 suffix specifies the underlying integer type. Any integer type works:

tape
enum BigEnum : u32 {
    First = 0;
    Last = 0xFFFFFFFF;
}

enum Sign : i8 { Minus = -1; Zero = 0; Plus = 1; }

Matching on enums

Use match to branch on enum values:

tape
match (facing) {
    Direction.North => { move(0, -1); }
    Direction.East => { move(1, 0); }
    Direction.South => { move(0, 1); }
    Direction.West => { move(-1, 0); }
}

Use _ as a wildcard catch-all:

tape
match (facing) {
    Direction.North => { move(0, -1); }
    _ => { io.println("not north"); }
}

Match must be exhaustive — all variants must be covered, or _ must be present. An arm can list several variants: Direction.East, Direction.West => { ... } (see Control flow).

As error types

Enums are commonly used as error types with T or E:

tape
enum ParseErr : u8 {
    BadInput = 1: "invalid input";
    Overflow = 2: "value too large";
}

fn parse(s: string) -> i64 or ParseErr {
    // ...
}

Last modified: