v0.4.0

Enums

An enum declares a closed set of named values, its cases, stored as an integer. A Direction is North, East, South or West and nothing else, and the compiler knows every case, so a match can be checked for completeness. An enum case carries no data; cases with data belong to a variant.

enum = [ attributes ] [ "pub" ] "enum" name [ ":" integer-type ] "{" [ case { "," case } [ "," ] ] "}"
case = name [ "=" constant-expression ]
enum Direction {
    North,
    East,
    South,
    West
}

enum Status: uint16 {
    Ok = 200,
    Created,
    NotFound = 404,
    ServerError = 500
}

Cases are separated by commas, and a comma may follow the last. By convention the type and its cases are PascalCase.

Backing type and values

The integer type after the colon is the enum's backing type: every value of the enum is stored as one value of it, so sizeof(Status) is 2. It must be an integer type — enum 'Wide' base type must be an integer type otherwise. Without one, the backing type is int.

Each case has an integer value. A case may give it with =; a case without one takes the previous case's value plus one, and the first case defaults to 0:

CaseValue
Direction::North0
Direction::West3
Status::Ok200
Status::Created201
Status::NotFound404

Values may be negative when the backing type is signed:

enum Ordering: int8 {
    Less = -1,
    Equal,
    Greater
}

Once other programs read these numbers — a file format, a network protocol, a C library — they are part of the program's interface. Inserting an unnumbered case in the middle renumbers every unnumbered case after it, so give each case whose value matters an explicit =.

Every value must fit the backing type, whether it is written with = or follows the previous case:

error: enumerator 'Size::Big' has the value 300, which the backing type 'uint8' cannot hold
  help: give it a value inside the backing type's range, or declare a wider backing type

A case without = after a uint8 case of 255 has the value 256 and is refused the same way.

Naming a case

A case is named through its type: Direction::North. Wherever the type the expression must have is already known, the short form .North names it without the type: an annotated let or var, a parameter, a field initializer, a returned value, an assignment to a typed place, and a pattern or arm of a match whose type is fixed. An optional destination names the type of its present value, so let heading: Direction? = .North; is a present Direction::North:

var heading: Direction = .North;
heading = .East;
let back = heading == Direction::East;   // true

The expected type is the only place a short case is looked up. Without one, let mode = .North; is refused, with a help naming Direction::North when exactly one type in scope declares the case:

error: case '.North' needs an expected type to name its enum or variant

A case on its own, North, is an unknown name.

Conversions

as converts in both directions between an enum and any integer type:

let code = Status::NotFound as uint16;   // 404
let received: uint16 = 404;
let status = received as Status;         // Status::NotFound

No other conversion applies: an enum does not convert to a float, and a case is not its number, so Status::Ok == 200 is operator '==' cannot compare left operand 'Status' with right operand 'int'.

as does not check. Converting an integer that is no case's value gives an enum value that is none of the cases. Nothing goes wrong until a match meets it: a match without a _ arm then stops the program with Panic: no match arm matched value of 'Status'. Check a number from outside the program before converting it.

Comparison

Enum values compare with ==, !=, <, <=, > and >=, which order them by their values:

let failed = status >= Status::NotFound;   // true for 404 and 500

Matching

A match on an enum must cover every case, or end with a _ arm. An arm names one case, or several joined with |:

func TurnRight(direction: Direction) -> Direction {
    return match direction {
        .North => Direction::East,
        .East => Direction::South,
        .South => Direction::West,
        .West => Direction::North
    };
}
error: match on 'Direction' is not exhaustive; missing Direction::West

Leaving _ out when every case matters means adding a case later makes every incomplete match stop compiling, at the place that needs the new arm. See match.

Methods

An enum can be extended with methods like any other type. A receiver taken by value, self, is natural for something as small as a number:

extend Direction {
    func Opposite(self) -> Direction {
        return (((self as int) + 2) % 4) as Direction;
    }
}

An enum value cannot be printed with {} directly; give it a method that returns its name.

Rules and errors

MistakeError
A case with a payload, Circle(float64)enum 'Shape' cannot declare payloads, with help: use 'variant' for cases that carry data
Type parameters, enum Gen<T>enum 'Gen' cannot declare type parameters
A non-integer backing typeenum 'Wide' base type must be an integer type
A case declared twiceduplicate enumerator 'A' in enum 'Twice'
The short form with no expected typecase '.North' needs an expected type to name its enum or variant
A missing case in a matchmatch on 'Direction' is not exhaustive; missing Direction::West

See also