Enums

Enums define a type that can be one of several named values. They make your code more readable and catch mistakes at compile time.

Defining an Enum

enum Direction {
    North
    South
    East
    West
}

fn main() {
    var dir:Direction = East
    print("Direction: {dir}\n")
}

Members are listed one per line. Each member is automatically assigned an integer value starting from 0.

Using Enums in Conditions

enum Color { Red Green Blue }

fn main() {
    var c:Color = Green

    if (c == Green) {
        print("Go!\n")
    } elif (c == Red) {
        print("Stop!\n")
    } else {
        print("Something else\n")
    }
}

Enums with Match

Enums and match are a natural pair:

enum Season { Spring Summer Autumn Winter }

fn describe(s:Season) {
    match (s) {
        Spring: print("Flowers blooming\n")
        Summer: print("Sun shining\n")
        Autumn: print("Leaves falling\n")
        Winter: print("Snow falling\n")
    }
}

fn main() {
    describe(Spring)
    describe(Summer)
    describe(Autumn)
    describe(Winter)
}

No default needed — you’ve covered all variants.

Explicit Values

Assign specific integer values to members:

enum HttpStatus {
    OK = 200
    NotFound = 404
    ServerError = 500
}

fn main() {
    var status:HttpStatus = OK
    print("Status: {status}\n")  // 200
}

Practical Example: State Machine

Enums are perfect for tracking states:

enum GameState { Menu Playing Paused GameOver }

struct Game {
    state:GameState
    score:i32
}

ex Game {
    fn update() {
        match (state) {
            Menu: {
                print("Press ENTER to start\n")
            }
            Playing: {
                score += 10
                print("Playing... Score: {score}\n")
            }
            Paused: {
                print("Game paused. Score: {score}\n")
            }
            GameOver: {
                print("Game Over! Final score: {score}\n")
            }
        }
    }
}

fn main() {
    var game = Game{Menu, 0}
    game.update()

    game.state = Playing
    game.update()
    game.update()

    game.state = Paused
    game.update()

    game.state = GameOver
    game.update()
}

Practical Example: Direction Movement

enum Direction { North South East West }

struct Position {
    x:i32
    y:i32
}

fn move_pos:Position(pos:Position, dir:Direction) {
    var new_pos = pos
    match (dir) {
        North: new_pos.y = new_pos.y + 1
        South: new_pos.y = new_pos.y - 1
        East:  new_pos.x = new_pos.x + 1
        West:  new_pos.x = new_pos.x - 1
    }
    return new_pos
}

fn main() {
    var pos = Position{0, 0}
    print("Start: ({pos.x}, {pos.y})\n")

    pos = move_pos(pos, North)
    print("After North: ({pos.x}, {pos.y})\n")

    pos = move_pos(pos, East)
    pos = move_pos(pos, East)
    print("After 2x East: ({pos.x}, {pos.y})\n")
}

Try it — Build a state machine with enums in the Playground.


Expert Corner

Enums compile to C typedef enum — they’re just named integers at runtime. Direction.North is 0, Direction.South is 1, and so on. Zero abstraction cost, zero overhead. A Direction variable is just an int in the generated C.

Members separated by newlines, commas optional: GX prefers newline-separated enum members. This produces cleaner version control diffs — adding or removing a member changes exactly one line, with no trailing comma adjustments. Commas are accepted but not required.

extern enum with @c_prefix is how GX wraps C library enums. For example, Sokol’s key codes:

@c_prefix("SAPP_KEYCODE_")
extern enum sapp_keycode {
    SPACE = 32
    RIGHT = 262
    LEFT = 263
    UP = 265
    DOWN = 264
}

This generates #define macros to bridge C naming conventions. In your GX code, you write SPACE instead of SAPP_KEYCODE_SPACE. The compiler handles the translation.

Enum members are visible across files: When you define an enum in one file and import it in another, all members are available without qualification. This is resolved in the compiler’s first pass to ensure cross-file visibility works correctly.