Proposal: Compile-Time Mutable Variables (comptime var)
Summary
Introduce comptime var to allow mutable state during the compilation phase. This feature, inspired by Zig, enables advanced metaprogramming patterns—such as compile-time counters, type registries, and iterative code generation—that are currently difficult or impossible with immutable alias definitions.
Motivation
Currently, Mojo’s compile-time metaprogramming relies on alias (or param), which are effectively immutable constants. While safe, this creates friction for advanced use cases:
- Global Counters: Impossible to generate unique, sequential IDs for types or functions at compile time.
- Accumulation: Difficult to “collect” items (like plugin registrations or test cases) into a list across different scopes.
- Complex Logic: Algorithms requiring state (like backtracking during macro expansion) are cumbersome to implement using only recursion and immutable parameters.
Proposed Syntax & Semantics
We propose the comptime var declaration. Unlike alias, which binds a name to a static value, comptime var binds a name to a value stored in the compiler’s memory context.
Basic Usage
# Define a mutable variable available only at compile time
comptime var build_counter: Int = 0
fn generate_unique_id() -> Int:
# Modify the state
build_counter += 1
return build_counter
fn main():
# These resolve at compile-time to 1 and 2 respectively
alias id_a = generate_unique_id()
alias id_b = generate_unique_id()
Scope
- Block Scope:
comptime varinside a function works like a standard variable but is evaluated and discarded during compilation. - Global Scope: Allows for cross-function state accumulation (e.g., registering types).
Comparison: The Zig Pattern
In Zig, comptime var is essential for iterating over types or creating compile-time logic that mimics runtime logic.
Zig Style:
comptime var i = 0;
inline while (i < 10) : (i += 1) { ... }
Mojo Equivalent:
Currently, Mojo requires recursive unrolling or @unroll. With comptime var:
comptime var i = 0
# A theoretical compile-time loop structure
while i < 10:
generate_code(i)
i += 1
Implementation Challenges
Implementing this is non-trivial due to Compiler Determinism.
- Order of Evaluation: To ensure builds are reproducible, the compiler must guarantee a strict order of operations. If
generate_unique_id()is called in two different modules, the resulting IDs must be deterministic based on the import graph. - Parallel Compilation:
comptime varintroduces data dependencies that may inhibit parallel compilation of source files. - Lifecycle: Clear rules must be defined for when the variable is initialized and destroyed (e.g., per module vs. global compilation context).
Conclusion
While implementing comptime var requires solving complex resolution ordering constraints, the payoff is significant. It unblocks powerful metaprogramming capabilities, allowing libraries to implement zero-cost abstractions that currently require external source-generation tools.