with Is a Copy of a Value With Named Fields ChangedStatus: Accepted
Date: September 16, 2026
Related: ADR-298 D8 (the word was kept for this),
ADR-298 (a word is reserved for a construct), ADR-094
and ADR-282 (a value is moved or viewed as the compiler decides,
and a copy is written), ADR-281 (update fn(mut v), the same
idea for a value behind a lock), Part I 2.1 (mut), 4.1 and 4.2 (structs and
their literals), 9.2 (private fields, NK1110)
Every binding in this language is immutable unless it says mut, and the
value a program wants most often is the one it has, with one field
different. There was no way to write that short of a struct literal naming
every field again:
let moved = Point(x: p.x + 1, y: p.y, z: p.z)
Three fields are bearable; a Stats with six is a line nobody reads for its
one change. C# has with for records, Kotlin has copy(x = …), Rust has
Point { x: 1, ..p } — and this language had the word reserved, unused, for
exactly this shape.
value with { field: expr, … } is a new value of the same type, with the named fields changed and the rest taken from value.let moved = p with { x: p.x + 1 }
let stats = old with { count: old.count + 1, sum: old.sum + t }
let named = user with { name } // shorthand: the local `name`
The braces are the struct literal’s, with its field list and its shorthand;
a field named twice, a field the type does not have, and a with that names
no field at all are refused, the last because a copy that changes nothing is
a line the reader would puzzle over. The left operand is any expression of a
struct type — a name, a field, a call.
with.p with { pos.x: 1 } is not a form. A field of a field is changed by
changing the field: p with { pos: p.pos with { x: 1 } }. One rule, one
level, and the question of what a path means through a T?, an enum or a
list never has to be answered.
What with does not name it takes from the left operand, by move — Rust’s
..p below. Where every such field copies (numbers, bool, char, a view),
the operand is usable afterwards, as it is anywhere. Where one does not — a
text, a list, a handle — and the operand is used again, the refusal names the
copy to write, exactly as a value used after a move is refused elsewhere. No
copy is inserted that the program did not write (ADR-282 D7).
Across a package boundary with reaches pub fields and nothing else, the
rule a struct literal has (NK1110). A type with a private field can still be
copied with a public one changed, because the private field is not named,
only carried.
with on an enum value. m with { x: 1 } where m is a Message
cannot be typed without knowing the variant; whether a with inside a
match arm — where the variant is known — should be allowed is left open,
and until then an enum operand is refused with a message that names match
(§5).with may one day replace the leading ; of an options-only call.
Two meanings for one word is what the language avoids, and this record
gives it one.NK1107, the literal’s own refusal; D4’s privacy is NK1110, the same. A
field named twice is NK1172 and a with that names none is
NK1174; the field-named-twice rule is the struct literal’s too, held in
one place for both.NK1173 is the operand, refused for one of four reasons: an enum (§4,
and the way out names match, where a variant is known); a view, because
D3 takes the unnamed fields by move and no copy is inserted that the program
did not write (ADR-282 D7); a type with no fields; and a value
whose type the compiler did not work out, because the lowering — Rust’s
Point { x: 1, ..p } — writes the type’s name...p is the move, and the analysis that
refuses a value used after one is the existing one.