← Back to AILP Home

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