Immutability — fixed vs const
Nitpick distinguishes two flavours of "this binding will not be reassigned":
| Qualifier | Where it is legal | Diagnostic if misplaced |
|---|---|---|
fixed |
Nitpick-side bindings only | (none — fixed rejects reassignment regardless) |
const |
Inside extern "lib" { ... } only |
ARIA-044 if used outside extern |
Decision D-13 / D-14 (Phase 3): the two qualifiers do not
overlap. Use fixed in your own .npk files; use const only when
naming a C ABI surface inside an extern block.
fixed — the Nitpick-side immutability qualifier
func:main = int32() {
fixed int32:answer = 42i32;
println(`{answer}`);
pass 0i32;
};
Reassigning a fixed binding is a compile error:
func:main = int32() {
fixed int32:x = 1i32;
x = 2i32; // error: Cannot reassign fixed variable 'x'
pass 0i32;
};
Compound-assignment (x += 1i32, x *= 2i32, …) is rejected on the
same grounds.
fixed + Drop — Drop still fires (D-22)
A fixed binding of a Drop-implementing type still has its
drop method called at scope exit. Immutability is about
reassignment, not lifetime.
struct:Counter { int32:value, };
impl:Drop:for:Counter { drop = NIL($$m self) { println(`drop`); }; };
func:main = int32() {
fixed Counter:c = Counter{ value: 0i32 };
// c.value = 1i32; // would be rejected
pass 0i32;
}; // <-- @Counter_drop(&c) runs here
This was verified by --emit-llvm inspection at v0.31.2.9
(see META/NITPICK/ROADMAP/0.31/AUDIT_v0.31.2.0.md D-22 entry).
fixed + generic T (D-19)
Immutability is a property of the binding, not of the type. A
fixed T:x = ...; for a generic T rejects reassignment at every
monomorphisation, both for primitive and struct T.
const — the extern-only qualifier
const is reserved for naming C ABI constants inside extern blocks:
extern "libc" {
const int32:RAND_MAX = 0i32; // imported from <stdlib.h>
};
Outside extern, const is an error:
func:main = int32() {
const int32:x = 10i32; // error: ARIA-044
pass 0i32;
};
error: ARIA-044: `const` is reserved for extern blocks — use
`fixed` for Nitpick-side immutability
--> example.npk:2:5
|
2 | const int32:x = 10i32;
| ^^^^^
The reverse rule is symmetric — putting fixed inside an extern
block is rejected with the mirror diagnostic.
Migration note (v0.31.2.1)
At v0.31.2.1 a 36-file sweep migrated const TYPE:NAME = ... →
fixed TYPE:NAME = ... across the stdlib and the bug fixtures.
Existing user code that used const Nitpick-side must do the same.
Comptime-fold interaction
fixed opportunistically participates in comptime evaluation: when the
initialiser is a comptime-evaluable expression, the compiler folds it
and treats the binding as a comptime constant for the rest of the
function. Runtime initialisers are still permitted; the fold simply
does not fire.
fixed int32:N = 8i32 * 4i32; // folded to 32i32 at compile time
fixed int32:from_io = read_int_from_stdin(); // runtime init — fold skipped
Fixtures
bug379…bug382—constoutside extern rejected;fixedinside extern rejected; the 36-file sweep regression (run_bug_tests_03121.sh).bug400…bug402—fixed T:x× generic monomorphisation regression (run_bug_tests_03126.sh).bug414…bug417—fixed× Drop verification (run_bug_tests_03129.sh).