Proposal: `iota`, an Auto-Incrementing Counter for `comptime` Struct Members

Proposal: iota, an Auto-Incrementing Counter for comptime Struct Members

Summary

Add iota as a built-in compile-time Int for struct comptime declarations. Inside a constructor expression T(...), iota evaluates to the previous member’s inner value plus 1 (starting at 0). An explicit literal resets the counter. This eliminates manual ID maintenance for enum-like structs without requiring new block syntax.

The Problem

Mojo lacks built-in enums, requiring enum-like types to be declared manually via comptime struct instances:

@fieldwise_init
struct Token(ImplicitlyCopyable, Equatable, Writable):
    var value: Int

    # --- non-lexeme tokens ---
    comptime EOF = Token(0)
    comptime Invalid = Token(1)
    comptime Identifier = Token(2)
    comptime NumericLiteral = Token(3)
    comptime StringLiteral = Token(4)

    # --- punctuation ---
    comptime LBrace = Token(10)
    comptime RBrace = Token(11)
    comptime LBracket = Token(12)
    comptime RBracket = Token(13)
    comptime LParen = Token(14)
    comptime RParen = Token(15)
    comptime Semicolon = Token(16)
    comptime Comma = Token(17)
    comptime Colon = Token(18)
    comptime QuestionMark = Token(19)

Only 0 and 10 represent intentional domain decisions; all other numbers are sequential maintenance overhead. Inserting a single member requires cascading edits:

    comptime Comma = Token(17)
    comptime Dot = Token(18)           # new
    comptime Colon = Token(19)         # was 18
    comptime QuestionMark = Token(20)  # was 19

Missing an edit produces silent collisions where distinct identifiers share an integer value.

Semantics

Within a struct scope:

  1. Initial Value: The first evaluation of iota in a comptime declaration resolves to 0.

  2. Auto-Increment: In successive comptime declarations of the same type T, iota evaluates to the evaluated integer argument of the previous declaration plus 1.

  3. Explicit Reset: Supplying an explicit integer literal (e.g., Token(10)) sets the sequence baseline; subsequent uses of iota continue from 10 + 1.

Example

@fieldwise_init
struct Token(ImplicitlyCopyable, Equatable, Writable):
    var value: Int

    # --- non-lexeme tokens (0 to 4) ---
    comptime EOF = Token(iota)             # 0
    comptime Invalid = Token(iota)         # 1
    comptime Identifier = Token(iota)      # 2
    comptime NumericLiteral = Token(iota)  # 3
    comptime StringLiteral = Token(iota)   # 4

    # --- punctuation (starts at 10) ---
    comptime LBrace = Token(10)            # 10, sets new base
    comptime RBrace = Token(iota)          # 11
    comptime LBracket = Token(iota)        # 12
    comptime RBracket = Token(iota)        # 13
    comptime LParen = Token(iota)          # 14
    comptime RParen = Token(iota)          # 15
    comptime Semicolon = Token(iota)       # 16
    comptime Comma = Token(iota)           # 17
    comptime Colon = Token(iota)           # 18
    comptime QuestionMark = Token(iota)    # 19

Inserting or removing tokens becomes an isolated, single-line operation:

    comptime Comma = Token(iota)
    comptime Dot = Token(iota)             # new (18)
    comptime Colon = Token(iota)           # automatically 19
    comptime QuestionMark = Token(iota)    # automatically 20

Advantages

  • Prevents Collisions: Eliminates duplicate value bugs caused by manual renumbering errors.

  • No Syntax Overhead: Requires no block indentation or macro DSLs; fits Mojo’s standard single-line comptime assignments.

  • Zero Runtime Cost: Fully evaluated and materialized at compile time.

Enum is being worked on: Pattern matching + Enum proposals

An enum describes an algebraic data type with a payload, whereas my proposal is aimed at representing a finite set of values within an integer range. The distinction matters because using Int as storage gives you full control over the layout, whereas with enum, the compiler has full control over the layout, including unused bits and niche optimizations (e.g., using spare bits to remove or represent the discriminant).

Yup. Sadly, the current enum proposal doesn’t do much at all if you’re interested in closed sets of values, as the OP seems to be here.

I see a lot of scepticism here. Let me explain:

First of all, iota is a feature borrowed from the go programming language This it inlines with mojo’s goal of only implementing proven features.

Second, the main distinction between iota with an EnumLike struct an an enum is between who owns the spare bits and thus whether you are allowed to bitcast. An alternative would be to have a @non_nichable_bitcastable_storage decorator. However this would interfere with niche optimizations and would take away the focus of optimizing your code whereas an explicit iota EnumLike struct would make the storage explicit.