Mutability of Structs
For a simple value like an int, the choice between let and var only decides whether the variable can be reassigned. For a struct it decides more: because a struct is a value type, the binding that holds it governs every field. Mutability is deep — there is no way to have an immutable struct with mutable fields, or the reverse.
struct Point {
x: float64;
y: float64;
}
let — Fully Immutable
A struct bound with let cannot be reassigned, and none of its fields can be modified:
let a = Point { x: 1.0, y: 1.0 };
a = Point { x: 0.0, y: 0.0 }; // error: cannot reassign a let binding
a.x = 2.0; // error: cannot modify a field of an immutable struct
var — Fields and Whole Value Both Mutable
A struct bound with var allows both field-level writes and whole-value reassignment:
var b = Point { x: 0.0, y: 0.0 };
b.x = 3.0; // modify one field
b.y = 4.0;
b = Point { x: 1.0, y: 1.0 }; // or replace the entire value
Nested Structs
The rule applies through nesting: a field that is itself a struct is mutable only when the top-level binding is var. There is no per-field opt-in or opt-out.
struct Line {
start: Point;
end: Point;
}
var line = Line {
start: Point { x: 0.0, y: 0.0 },
end: Point { x: 1.0, y: 1.0 },
};
line.start.x = 5.0; // OK — reached through a var binding
If line were a let, line.start.x = 5.0 would be rejected just like a direct field write.
Mutating Through a Pointer
A pointer does not bypass immutability. Only a writable *var T pointer can write through to the pointee, and @ produces one only when the addressed value is itself a var. Taking @ of a let yields a read-only *T:
var p = Point { x: 0.0, y: 0.0 };
let ptr = @p; // *var Point — p is var
ptr.x = 5.0; // OK
let q = Point { x: 0.0, y: 0.0 };
let qp = @q; // *Point — q is let, so read-only
qp.x = 5.0; // error: cannot assign through a pointer to immutable data
This is also why an instance method that mutates self can only be called on a var receiver — see Methods.
Other Composite Types
The same deep rule covers the other value types: a tuple or a fixed-size array (T[N]) is mutable only when bound with var.
A dynamic slice (Slice<T>) is the exception, because it is a view rather than a value: a let slice fixes the header (its data pointer and length) while the elements it refers to may still be written through it. Bind the slice with let to keep it pointing at the same region; the mutability of the elements depends on the storage they live in.
See Also
- Immutable Variables (
let) — the default binding - Mutable Variables (
var) — enabling field changes - Structures — value semantics and field access
- Pointers to Fields and Members — writing through a pointer
Mutable
A variable introduced with var is mutable: it can be reassigned at any time after declaration. Use it for values that genuinely change — counters, accumulators, loop state — and prefer let for everything else.
Overview
A constant binds a name to a value evaluated at compile time. Constants are declared with const, are always immutable, and must have an explicit type annotation. Unlike let bindings, they may be declared at module scope, which makes them the natural home for shared limits and mathematical values.