ktm v0.28.3

Enums

An enum is a set of named integer constants of its own type. Its members are always written with the enum's name in front, Color.red, so two enums can have members of the same name. This chapter covers declaring enums, their values and underlying type, using members as constants, flags, conversions to and from integers, switching on an enum, names at run time, and enums as error codes.

Declaring an enum

enum Name { members }; declares the type Name and its members. A member is used as Name.member:

enum Color { red, green, blue };

i32 main() {
    Color c = Color.green;
    printf("%d %d %d\n", Color.red, c, Color.blue);
    return 0;
}
0 1 2

The bare member name isn't in scope outside the enum:

does not compileenum Color { red, green, blue };

i32 main() {
    printf("%d\n", green);
    return 0;
}
`green` has not been defined

So members of different enums never collide:

enum Light { off, on };
enum Fan { off, low, high };

i32 main() {
    printf("%d %d\n", Light.on, Fan.high);
    return 0;
}
1 2

Values

The first member is 0 and each next one is the previous plus 1, unless it is given a value:

enum Status { ok = 200, created, accepted, notFound = 404, gone };

i32 main() {
    printf("%d %d %d\n", Status.ok, Status.created, Status.accepted);
    printf("%d %d\n", Status.notFound, Status.gone);
    return 0;
}
200 201 202
404 405

Values can be negative, and a member's value can use earlier members without the prefix:

enum Temp { freezing = -10, cold = 0, warm = 20, hot };
enum Size { small = 1, medium = small * 4, large = medium * 4 };

i32 main() {
    printf("%d %d %d %d\n", Temp.freezing, Temp.cold, Temp.warm, Temp.hot);
    printf("%d %d %d\n", Size.small, Size.medium, Size.large);
    return 0;
}
-10 0 20 21
1 4 16

The underlying type

An enum is stored as an i32 unless : Type after its name picks another integer type:

enum Color { red, green, blue };
enum Status : u16 { ok = 200, notFound = 404 };
enum Big : i64 { huge = 5000000000 };

i32 main() {
    printf("%d %d %d\n", sizeof(Color), sizeof(Status), sizeof(Big));
    printf("%d\n", Big.huge);
    return 0;
}
4 2 8
5000000000

A member that doesn't fit the type is an error:

does not compileenum Tiny : u8 { a, b = 255, c };

i32 main() {
    return 0;
}
value 256 does not fit in `u8`

Members are constants

Name.member is a constant expression, so it can size an array, initialise a const or a global, and label a case:

enum Color { red, green, blue, count };

u8 *names[Color.count] = {"red", "green", "blue"};
const i64 LAST = Color.blue;

i32 main() {
    printf("%d names, last %d\n", sizeof(names) / sizeof(u8 *), LAST);
    printf("%s\n", names[Color.green]);
    return 0;
}
3 names, last 2
green

A last member like count above is a common way to know how many members there are, as long as the others have the default values 0, 1, 2 ...

Switching on an enum

A switch with one case per member reads naturally; a case doesn't fall through into the next (see switch):

enum Color { red, green, blue };

u8 *mood(Color c) {
    switch (c) {
        case Color.red:   return "warm";
        case Color.green: return "calm";
        case Color.blue:  return "cool";
    }
    return "?";
}

i32 main() {
    printf("%s %s %s\n", mood(Color.red), mood(Color.green), mood(Color.blue));
    return 0;
}
warm calm cool

Conversions

An enum value converts to an integer by itself, in assignments, arithmetic and comparisons:

enum Color { red, green, blue };

i32 main() {
    Color c = Color.blue;
    i64 n = c;
    printf("%d %d %d\n", n, c * 10, c > Color.red);
    return 0;
}
2 20 1

The other way needs a cast: an integer isn't an enum value until it is said to be one.

does not compileenum Color { red, green, blue };

i32 main() {
    Color c = 1;
    return 0;
}
Cannot convert `i64` to enum `Color` without a cast
enum Color { red, green, blue };

Color next(Color c) {
    return (Color)((c + 1) % 3);
}

i32 main() {
    Color c = (Color)2;
    printf("%d %d\n", c, next(c));
    return 0;
}
2 0

The cast doesn't check the number: (Color)7 is a Color with no member of that value.

Arithmetic assignment (+=, ++) on an enum variable is refused for the same reason; cast, compute, and cast back:

does not compileenum Color { red, green, blue };

i32 main() {
    Color c = Color.red;
    c++;
    return 0;
}
Cannot do arithmetic assignment on enum `Color`
enum Color { red, green, blue };

i32 main() {
    for (Color c = Color.red; c <= Color.blue; c = (Color)(c + 1)) {
        printf("%d ", c);
    }
    printf("\n");
    return 0;
}
0 1 2 

Two different enums don't mix: one doesn't convert to the other, and comparing them is an error.

does not compileenum Color { red, green };
enum Size { small, large };

i32 main() {
    Color c = Size.large;
    return 0;
}
Cannot convert `Size` to enum `Color` without a cast
does not compileenum Color { red, green };
enum Size { small, large };

i32 main() {
    return Color.green == Size.large;
}
Cannot compare enum `Color` with enum `Size`

Flags

Members whose values are powers of two can be combined with | and tested with &. The combination is an integer, not a member:

enum Perm { read = 1, write = 2, exec = 4, all = read | write | exec };

i32 main() {
    i64 p = Perm.read | Perm.write;
    printf("can write: %d\n", (p & Perm.write) != 0);
    printf("can exec: %d\n", (p & Perm.exec) != 0);
    printf("all: %d\n", Perm.all);
    p &= ~Perm.write;
    printf("after removing write: %d\n", p);
    return 0;
}
can write: 1
can exec: 0
all: 7
after removing write: 1

Enums in structs

An enum field takes the enum's size; a struct initialiser list takes a member for it:

enum Suit { hearts, spades };

struct Card {
    Suit suit;
    i64 rank;
};

i32 main() {
    Card c = {Suit.spades, 12};
    printf("%d %d, %d bytes\n", c.suit, c.rank, sizeof(Card));
    return 0;
}
1 12, 16 bytes

An enum can't have methods of its own; a function that takes it does the same job:

does not compileenum Color { red, green };

void Color.print() {
}

i32 main() {
    return 0;
}
only a struct or union can have methods

Names at run time

printf prints an enum's number. reflect.name gives the member's name, and reflect.enumValue finds the value of a name (see Reflection):

import reflect;

enum Role { guest, member, admin = 10 };

i32 main() {
    Role r = Role.admin;
    printf("%d %s\n", r, reflect.name(r));
    i64 v;
    if (reflect.enumValue(reflect.typeOf(Role), "member", &v)) {
        printf("member is %d\n", v);
    }
    printf("[%s]\n", reflect.name((Role)7));
    return 0;
}
10 admin
member is 1
[]

A value that isn't a member has the empty name. JSON and YAML write an enum field as its member's name and read it back the same way (see Attributes).

Enums as error codes

An enum is the usual code of an error (see Errors):

enum ParseError { empty, notNumber };

i64 parse(u8[] s) {
    if (!s.len) {
        throw(ParseError.empty, "empty input");
    }
    i64 n = 0;
    for (i64 i = 0; i < s.len; i++) {
        u8 c = s[i];
        if (c < '0' || c > '9') {
            throw(ParseError.notNumber, "bad digit '%c'", c);
        }
        n = n * 10 + c - '0';
    }
    return n;
}

i32 main() {
    u8[] inputs[3] = {"42", "", "4x"};
    for (i64 i = 0; i < 3; i++) {
        try {
            printf("%d\n", parse(inputs[i]));
        } catch (Error e) {
            printf("error %d: %s\n", e.code, e.message);
        }
    }
    return 0;
}
42
error 0: empty input
error 1: bad digit 'x'

Rules