Syntax
Constant Expressions
A constant expression is an expression the compiler computes at compile time. Constants carry everything the story cannot hold at runtime, such as durations, colours and renders, and they let the compiler check values such as text effects, motions and asset paths before the game runs.
Definition
An expression is constant when it is one of:
- a literal;
- a path to a
constitem, a unit struct or unit variant, a named function or a tuple struct or tuple variant constructor, a const generic parameter, an immutablestatic(aSettingorMetahandle; the value is the handle itself), or a constant local (below); - a struct, enum, tuple, array or range expression, an operator (unary, binary, index, shared
borrow
&), a field access, a block, anif,if letor amatch, whose operands are all constant. The statements of such a block areletstatements with a constant initializer that bind only immutable variables, items, and constant expression statements; - a call of a
const fnor of a tuple constructor whose arguments are all constant. In a method call the receiver counts as an argument, and it must not be borrowed mutably: a call of a&mut selfmethod is not constant; asset::<A>("…"), which is such a call;- a closure that captures nothing.
A constant local is an immutable binding of a let whose initializer is constant, or of an
if let or match arm pattern whose scrutinee is constant.
These are never constant: parameters, story variables (static mut), let mut bindings, the
results of fn and pushdown fn calls, loops, break, continue, return, ?, assignments,
&mut borrows and closures that capture variables.
const FADE: Transition = fade(ms(500)); // a const fn call with constant argumentsconst CENTER: Point = point(1280 / 2, 720); // an operator on literalsconst DIM: Color = { let base = rgb(40, 40, 60); // a constant local Color { r: base.r, g: base.g, b: base.b, a: 160 }};const fn calls
A call of a const fn with constant arguments is a constant expression: the compiler runs the
function and uses its result. The function’s body does not have to be a constant expression
itself. It may use let mut, loops and assignment; it only has to be a const fn, which is pure
(see Function Kinds).
const fn triangle(n: u32) -> u32 { let mut total = 0; for i in 1..=n { total += i; } total}
const T10: u32 = triangle(10); // 55, computed at compile timeA compile-time evaluation runs at most 1 000 000 steps (E4003).
Where constant expressions are required
| Where | Requirement | Diagnostic |
|---|---|---|
Initializer of a const item | A constant expression | E3010 |
Initializer of a story variable (static mut) | A constant expression | E3010 |
Initializer of a handle (immutable static) | A constant expression that makes a Setting or Meta handle | E3010, E3011, E4007 |
Array length [T; N], repeat count [value; N] | A type-level constant | E2002, E2003 |
Bounds of Int<lo, hi> | Type-level constants that fit i64, with lo <= hi | E2002, E2003, E2004 |
| Const generic arguments | Type-level constants | E2002, E2003 |
Effect of a text effect  in t![…] | A constant expression of type TextEffect | E3022 |
A value of a non-finite type in a pushdown fn | A constant expression | E3008 |
Range bounds of a for loop in a pushdown fn | Constant expressions | E3006 |
Path of asset::<A>(…) and system_font(…) | A string literal | E2028 |
id of choice!{…} | A string literal | E0001, E3019 |
Constants in the story
The story holds only values of finite types at runtime, but a
constant expression of any type may appear in a pushdown fn, because the compiler computes it.
render = fade_to_room, fade(ms(500)) and rgb(200, 0, 0) are all fine in a scene; a Duration
computed from a story variable is not (E3008).
In a pushdown fn, every largest constant subexpression is computed at compile time, so its
errors (such as an overflow or an invalid motion) are compile errors, not run-time panics. A call
of a const fn whose arguments are not all constant, but all of finite types, is computed at
compile time for every possible argument value instead; see
Calling const fn from the story.
Type-level constants
Array lengths, Int bounds and const generic arguments are part of a type, so the compiler needs
their values while it checks types. They use a smaller language:
- integer literals;
- unary
-and the operators+,-,*,/,%; - a block
{ … }that holds only a final expression; - a const generic parameter by itself (
N, but notN + 1); - a path to a
constitem whose initializer is itself a type-level constant.
Anything else, including a const fn call, is E2002; so is a const that depends on itself.
Overflow, division by zero or an integer literal too large is E2003. A const generic argument
other than a literal is written in braces:
const SLOTS: usize = 4;const MAX_LOVE: i64 = 10;
static mut FLAGS: [bool; SLOTS] = [false; SLOTS];static mut LOVE: Int<0, { MAX_LOVE * 2 }> = 0;Const-evaluation errors
When the compiler computes a constant and the computation fails, the error is a compile error with
an E4xxx code. See Constant Evaluation (E4xxx) for examples and fixes.
| Code | Error |
|---|---|
E4001 | An integer result does not fit its type |
E4002 | Division by zero, including Rat::new(n, 0) |
E4003 | The evaluation ran more than 1 000 000 steps |
E4004 | A const fn call from the story has more than 65 536 argument combinations to compute |
E4005 | An index past the end of an array or Vec |
E4006 | A const whose value depends on itself |
E4007 | A Setting or Meta handle made outside the initializer of a static |
E4008 | A malformed motion or shake |
E4009 | An easing with parameters out of range |
E4010 | The same text mark twice in one text |
E4011 | begin_text while the timeline is anchored on a text mark |
E4012 | A fraction outside 0.0..=1.0, such as an opacity, a volume or an anchor |
E4013 | A Duration that does not fit in u32 milliseconds |
Last updated Oct 29, 2026