lus

Acquis 5 - Enums

Contents (15)
  1. Syntax
  2. Identity and equality
  3. Indexing
  4. Chained indexing
  5. Ordering
  6. Numeric conversion
  7. Type checking
  8. Usage patterns
    1. As function arguments
    2. As table values
    3. As function returns
  9. Motivation
    1. String constants
    2. Numeric constants
    3. Cross-enum safety

Enums are first-class values representing named constants. Each enum value is a symbol: it is only equal to other values from the same enum with the same name.

Syntax#

Declare an enum with the enum ... end expression:

local status = enum
    pending, active, completed, failed
end

Values are comma-separated names. The enum expression returns the first value:

local color = enum red, green, blue end
assert(color == color.red)  -- enum equals its first value

Identity and equality#

Enum values are symbols with identity semantics. Values from the same enum with the same name are equal:

local e = enum x, y, z end
assert(e.x == e.x)
assert(e.y == e.y)

Different values from the same enum are not equal:

local e = enum a, b, c end
assert(e.a ~= e.b)
assert(e.b ~= e.c)

Values from different enums are never equal, even with the same name:

local e1 = enum a, b, c end
local e2 = enum a, b, c end
assert(e1.a ~= e2.a)  -- different enums
assert(e1.b ~= e2.b)

Indexing#

Access enum values by name:

local fruit = enum apple, orange, banana end
local f = fruit.orange
assert(f == fruit.orange)

Access by 1-based numeric index:

local size = enum small, medium, large end
assert(size[1] == size.small)
assert(size[2] == size.medium)
assert(size[3] == size.large)

Any enum value can be indexed to access other values from the same enum:

local e = enum x, y, z end
assert(e.x.y == e.y)      -- name indexing from a value
assert(e.y.z == e.z)
assert(e[1][3] == e.z)    -- numeric indexing from a value

Chained indexing#

Values can be chained to navigate the enum:

local e = enum a, b, c end
assert(e.a.b.c == e.c)
assert(e.c.b.a == e.a)

Ordering#

Enum values can be compared by their position:

local priority = enum low, medium, high, critical end

assert(priority.low < priority.medium)
assert(priority.medium < priority.high)
assert(priority.high < priority.critical)

assert(priority.critical >= priority.high)
assert(priority.low <= priority.low)

Ordering uses the declaration position, not the name.

Numeric conversion#

Use tonumber to get the 1-based index of an enum value:

local state = enum idle, running, paused, stopped end

assert(tonumber(state.idle) == 1)
assert(tonumber(state.running) == 2)
assert(tonumber(state.paused) == 3)
assert(tonumber(state.stopped) == 4)

Type checking#

The type function returns "enum" for enum values:

local e = enum val end
assert(type(e) == "enum")
assert(type(e.val) == "enum")
assert(type(e[1]) == "enum")

Usage patterns#

As function arguments#

local mode = enum read, write, append end

local function openFile(path, m)
    if m == mode.read then
        -- read logic
    elseif m == mode.write then
        -- write logic
    end
end

openFile("data.txt", mode.read)

As table values#

local status = enum pending, approved, rejected end

local requests = {
    { id = 1, status = status.approved },
    { id = 2, status = status.pending },
}

for _, req in ipairs(requests) do
    if req.status == status.approved then
        process(req)
    end
end

As function returns#

local result = enum success, failure, timeout end

local function fetch(url)
    -- ...
    return result.success
end

if fetch("https://example.com") == result.success then
    print("Request succeeded")
end

Motivation#

Lua does not have a built-in enum type. Developers simulate enums using tables or string constants, but these approaches lack type safety.

String constants#

A common Lua pattern:

-- Lua: string constants
local STATUS_PENDING = "pending"
local STATUS_ACTIVE = "active"

local function setStatus(s)
    if s == STATUS_PENDING then
        -- ...
    end
end

setStatus("pending")  -- works
setStatus("penidng")  -- typo: no error, just wrong

With enums, typos cause immediate errors:

-- Lus: type-safe enums
local status = enum pending, active end

setStatus(status.pending)  -- works
setStatus(status.penidng)  -- error: nil field

Numeric constants#

Another Lua pattern:

-- Lua: numeric constants
local RED, GREEN, BLUE = 1, 2, 3

if color == RED then  -- is this a color or a count?

Enums are distinct types, preventing confusion:

-- Lus: enums are symbols
local color = enum red, green, blue end
local count = 1

assert(color.red ~= count)  -- never equal

Cross-enum safety#

Different enums are incompatible:

local status = enum ok, error end
local color = enum red, green end

-- Same position (1), but never equal
assert(tonumber(status.ok) == tonumber(color.red))
assert(status.ok ~= color.red)

This prevents accidentally mixing unrelated constants.