fixed struct fields
A struct field declared with the fixed qualifier is initialised
exactly once — at struct-literal construction time — and is rejected
by the compiler at every assignment site thereafter, including
through $m mutable borrows.
fixed-on-struct-field landed in v0.31.9.3 (ARIA-056,
PHASE3-DEC-007/008/009). Before that slice, fixed was binding-only;
struct fields silently ignored the qualifier.
Declaring a fixed field
struct:Counter {
fixed int32:id;
int32:count;
};
func:main = int32() {
Counter:c = Counter{ id: 42, count: 0 };
c.count = c.count + 1; // OK — count is not fixed
exit c.count;
};
The id field is initialised by the struct literal Counter{ id: 42, ... }
and may be read freely. Any subsequent write fails:
c.id = 99; // error[ARIA-056]: cannot assign
// to fixed field `id` of struct
// `Counter`
Mixed-mutability semantics
A struct with both fixed and ordinary fields is fully usable through
a $m borrow — the borrow does not "downgrade" the struct as a
whole. Only the fixed fields are rejected on assignment; ordinary
fields write through the mutable borrow as usual.
func:bump = NIL($m Counter:c) {
c.count = c.count + 1; // OK
// c.id = 99; // would be ARIA-056
pass NIL;
};
The reverse — a $i immutable borrow over the whole struct — still
rejects writes to any field (ordinary or fixed), as before.
Construction is the only write
fixed does not mean "compile-time constant" — the initial value
can be any runtime expression, as long as it appears in the struct
literal:
func:main = int32() {
int32:seed = read_seed();
Counter:c = Counter{ id: seed, count: 0 };
exit c.id;
};
The field is bound at the moment the literal is evaluated. After that, the binding is treated as fixed for the lifetime of the struct instance.
Interaction with ..base struct update
Struct-update syntax Counter{ ..base, count: 5 } constructs a new
instance; it is not an assignment to base. The new instance gets
its id from base.id, but it is being initialised, not reassigned,
so the rule allows it:
func:main = int32() {
Counter:original = Counter{ id: 42, count: 0 };
Counter:next = Counter{ ..original, count: 7 }; // OK
exit next.id; // 42
};
A field-list that omits a fixed field but explicitly names it in
the update list is rejected — you cannot use ..base as a workaround
to write a new value into a fixed field of the same instance.
Diagnostic
error[ARIA-056]: cannot assign to fixed field `<field>` of struct `<Struct>`
--> source.npk:LINE:COL
|
LL | c.id = 99;
| ^^^^ fixed at struct-literal construction
= help: fixed fields can only be initialised inside the
struct literal; remove `fixed` from the field
declaration to allow reassignment.
Regressions
bug509— bare assignment to a fixed field rejected.bug510— assignment through a$mborrow rejected.bug511— assignment through nested struct path rejected.bug512— struct-update..baseconstructs a fresh instance cleanly (positive control).
Cross-references
- Runtime taint and
T?unknown— companion feature landed in the same sub-cycle. - Special values: immutability —
fixedvsconst— the original binding-levelfixedstory.