Const bindings¶
const declares a compile-time constant: a name whose value is validated during compilation and can be baked into the
output program.
Why is const a thing?¶
In Incan, const exists to make intent explicit and to unlock compiler guarantees:
- Intent: communicates “this never changes” to readers and tools.
- Safety: prevents accidental mutation/reassignment and enforces deep immutability for baked data.
- Performance: allows the compiler to bake data into the output (including
'staticbacking data when needed). - Reproducibility: keeps “configuration tables” and other fixed data out of runtime control flow.
Coming from Python?
In Python, “constants” are mostly a convention (e.g. MAX_RETRIES = 5) and can still be reassigned at runtime.
In Incan, const is a language feature with compile-time validation and immutability guarantees.
How do I use it?¶
- Syntax:
const NAME [: Type] = <expr> - Scope:
constis currently module-level only (see RFC 008) - Type annotations: optional; if omitted, the compiler must be able to infer the type
- Initializer rules: the initializer must be const-evaluable (no runtime calls)
Rule of thumb
If you can’t evaluate the initializer without running the program (IO, time, function calls, loops), it doesn’t belong
in a const.
How is const different from a regular variable?¶
const:- evaluated at compile time
- cannot depend on runtime values or non-const variables
- deeply immutable (no mutating APIs for baked strings/bytes/collections)
- regular variables (
let/ bindings):- computed at runtime
- can call functions, use loops, read inputs, etc.
- may be mutable (
mut) depending on the binding
Coming from TS/JS?
In TypeScript/JavaScript, const mostly means “this binding can’t be reassigned” (the value can still be mutable),
and it can be computed at runtime.
In Incan, const means “compile-time constant”: the initializer must be const-evaluable, and the result is exposed as
a read-only (frozen) value.
What can a const initializer do?¶
Allowed:
- literals:
int,float,bool,str,bytes - simple unary/binary ops on const literals:
+,-,*,/,%, comparisons, boolean ops, string concatenation - tuple/list/dict/set literals whose elements/keys/values are themselves const-evaluable
- references to other
constbindings
Disallowed:
- function/method calls (including builtins)
- comprehensions, ranges, f-strings
- accessing non-const variables or runtime state
If the initializer is not const-evaluable, the compiler emits an error at the binding site.
Why are there “Rust-native” vs “Frozen” consts?¶
Incan supports two const “families” because Rust’s const rules are strict and because Incan wants deep immutability
for baked data:
- Rust-native consts:
- map directly to a Rust
const - best for numbers, booleans, and tuples of those
- map directly to a Rust
- Frozen consts:
- for data that should be baked into the program but exposed through a read-only API
- includes strings/bytes and common containers (lists/dicts/sets)
- represented using frozen stdlib wrappers like
FrozenStr,FrozenBytes,FrozenList[T],FrozenDict[K, V],FrozenSet[T] - the compiler emits baked
'staticbacking data and constructs frozen wrappers from it
Important detail: in const context, Incan treats str/bytes and common containers as frozen to preserve deep
immutability. In other words, const is not “just a let that runs earlier”.
Coming from Rust?
This is similar to Rust’s split between “things that can be const” and “things that need 'static backing data and
a safe API”.
Examples¶
Rust-native consts¶
const MAX_RETRIES: int = 5
const TIMEOUT_SECS: float = 2.5
const IS_DEBUG: bool = False
const PORT = 8080 # type may be inferred
const GREETING = "hello" + " world" # string concatenation is allowed
Frozen consts (deeply immutable)¶
const GREETING: FrozenStr = "hello"
const NUMS: FrozenList[int] = [1, 2, 3]
const HEADERS: FrozenDict[FrozenStr, FrozenStr] = {"User-Agent": "incan"}
const DATA: FrozenBytes = b"\x00\x01"
Consts can reference other consts¶
const BASE: int = 10
const LIMIT: int = BASE * 2
Errors¶
- If the initializer is not const-evaluable (calls, comprehensions, ranges, f-strings, non-const variables).
- If a const dependency cycle exists (consts reference each other in a loop).
- If types do not match (explicit type annotation incompatible with the initializer).