immutable Binds a Value Nothing Reachable From It Ever Changes; at Item Level It Is Evaluated Once, on First UseStatus: Accepted
Date: November 3, 2026 (owner’s decision)
Answers: #1174 point 3: a value the whole program reads, computed once at
run time (the compiler’s own accepted_dump_dir() reads an environment
variable once and keeps the answer, a Rust OnceLock), and a way to say inside
a function that a value never changes, deeply
Related: ADR-351 D8 (no global state), Part II 10.2
(comptime), Part I 2.1 (let, mut), Part I 6.2 (Shared, SharedMut,
Locked), Part II 11.2 (what a task may be handed)
let is shallow: the name cannot be assigned again, but a SharedMut it holds
can still be changed through another owner. comptime X = f() at item level is
evaluated at build time and cannot read what only the running program knows
(an environment variable, the path of the program). A Rust static with a
OnceLock is global state, which ADR-351 D8 refuses. How does a program say
“this value never changes”, and how does it keep a value computed at run time,
once, for every part of the program to read?
immutable is a reserved word and a bindingimmutable x = expr binds a name like let, inside a function body and where
an item stands. A type may be written, immutable x: T = expr. immutable mut
does not exist.
Neither the name nor anything reachable from its value ever changes. The value’s
type may not reach, through fields, elements, variants or captures, a
SharedMut, a Locked, a channel end, or another handle to storage shared and
changeable through another owner. Such a binding is refused with NK1331,
labelled at the initializer, naming the path to the first such part
(config.cache: SharedMut[Map[String, Int]]). A Shared[T] is allowed where
T itself passes the rule. Methods taking ref mut self cannot be called on
it, and it cannot be moved out of: it is read through views.
A task may read an immutable binding of the function that starts it, by
capture or as an argument, with no copy and no owner count: nothing can change
it, and the compiler keeps it alive until the last task that reads it has
ended (the scope rule of Part I 8.3). It never meets NK2101.
immutable X = f() where an item stands is evaluated on first access, exactly
once, even when several tasks reach it at the same time; the others wait for the
one that evaluates it. Every later access reads the same value. It is not
global state in the sense of ADR-351 D8: nobody can change it, so no part of
the program can affect another through it. An item-level immutable may be
pub. Its initializer may read other item-level immutables; a cycle among
them is refused at build time with NK1331, naming the cycle.
An item-level initializer whose call can fail is refused (NK1331, the help
writes a catch or a T?). A value that may be missing is written as T?:
immutable DUMP_DIR: String? = env::var("NIKAIA_DUMP_ACCEPTED").
A crash in the initializer is a crash of the program at first access.
comptime and let keep their meaningcomptime X = f() stays the form for values known at build time (Part II
10.2); let stays shallowly immutable. A value that is known at build time
and deeply immutable is written with comptime.
let.immutable is transitive in the same way, and Rust’s
LazyLock gives the same once-and-thread-safe evaluation.immutable on the reserved list (Part I 2.1).pub.NK1331), no ref mut self calls and no
moves out; the no-throw rule of D5 and the cycle rule of D4.NK2101 and no count for an immutable capture.LazyLock in the
emitted Rust), function level as a plain binding.tests/compiler/ for each rule and each refusal.accepted_dump_dir() in the move of check_walked (#125).