How do pure constructors work and why use ':=?' for field assignment?
← Purity Contracts · Ref: Q563
Pure constructors use ':=?' (guarded assignment) for field assignment, not ':' (direct assignment). The ':=?' operator only assigns if the variable is currently unset, preventing mutation.
WHY ':=?' IN PURE CONSTRUCTORS
In a pure constructor, mutation operators are forbidden (E08120). The ':' operator on a field that already has a default value counts as reassignment. The ':=?' operator is safe because it only assigns when the field is unset.
PATTERN FOR PURE CONSTRUCTORS
Declare fields with default values, then use ':=?' in the constructor:
Config
host as String: String()
port as Integer: Integer()
Config() as pure
-> host as String, port as Integer
this.host :=? host
this.port :=? port
WHY THIS WORKS
The fields must start UNSET - 'String()', 'Integer()' - and NOT a value such as 0 or "". A field given a value is already SET, so ':=?' can never fire and the constructor argument is silently discarded; the compiler rejects that as E08095. The ':=?' checks if the field is unset and assigns only then. After the first assignment, the field is set and ':=?' becomes a no-op. This is idempotent and non-mutating.
ALL OR NOTHING RULE
If ANY constructor is pure, ALL constructors must be pure (see Q564). This prevents inconsistent construction semantics.
See Q560 for pure method basics. See Q564 for all-or-nothing constructor purity. See Q565 for multiple pure constructors. See Q79 for guarded assignment operator.
Example
defines module qa.purity.constructorassign defines class Config host as String: String() port as Integer: Integer() //Pure constructor using :=? for field assignment Config() as pure -> host as String port as Integer this.host :=? host this.port :=? port host() as pure <- rtn as String: host port() as pure <- rtn as Integer: port default operator ? //Another example: immutable value object Point xCoord as Float: Float() yCoord as Float: Float() Point() as pure -> xCoord as Float yCoord as Float this.xCoord :=? xCoord this.yCoord :=? yCoord xCoord() as pure <- rtn as Float: xCoord yCoord() as pure <- rtn as Float: yCoord distanceFromOrigin() as pure <- rtn as Float: sqrt(xCoord * xCoord + yCoord * yCoord) default operator ? defines program PureConstructorAssignDemo() stdout <- Stdout() config <- Config("localhost", 8080) stdout.println(`Host: ${config.host()}, Port: ${config.port()}`) point <- Point(3.0, 4.0) stdout.println(`Point: (${point.xCoord()}, ${point.yCoord()})`) stdout.println(`Distance: ${point.distanceFromOrigin()}`)
Common mistakes
E08095 — Giving the field a value at declaration makes it SET, so the ':=?' in the constructor can never apply and the 'host' argument is silently discarded - the object keeps "localhost" whatever you construct it with. Fields assigned by a pure constructor must be declared UNSET. See ek9 -h E08095 for details.
Incorrect:
host as String: "localhost"
Correct:
host as String: String()
E08100 — In a pure constructor, direct assignment with : counts as reassignment of a field. The guarded assignment :=? must be used instead because it only assigns when the field is unset. See ek9 -h E08100 for details.
Incorrect:
this.host: host
Correct:
this.host :=? host
E50001 — Renaming the variable means later references to 'config' become unresolved, triggering E50001. See ek9 -h E50001 for details.
Incorrect:
configXYZ <- Config("localhost", 8080)
Correct:
config <- Config("localhost", 8080)
Other ways to ask this
- What is E08120 in a pure constructor?
- Why does my pure constructor reject ':' for field assignment?
- How do I assign fields in a pure constructor?
Coming from another language?
Java: no pure constructors, no guarded assignment. Python: no purity concept. Rust: constructors are always pure (no mutation after move). Kotlin: no pure constructors. EK9: pure constructors with ':=?' for idempotent field initialization, E08120 prevents mutation in pure context.
Keywords: assign, safe, mutation, side-effect, guarded, constant, E08120, immutable, purity, field, idempotent, constructor, pure, isset, null-safe, contract