E08258: data inside a MutexLock must be purely contained — assigning 'lockedItem' creates an external reference; use ':=:' (copy), ':~:' (merge), or ':^:' (replace) instead
What went wrong
Inside a MutexKey callback body, 'lockedItem' was reference-assigned (via ':=', '=', ':', or ':=?') to a target that survives the callback. The assignment creates a second reference to the lock's protected payload — data that should be PURELY CONTAINED within the MutexLock is now reachable from outside. The lock's exclusivity guarantee is silently defeated.
Rationale
A MutexLock's contract is that only one thread accesses the payload at a time, and that access happens only inside enter() / tryEnter() callbacks. That guarantee relies on the lock holding the ONLY reference to the payload. Any other reference — whether a captured variable, a field on a class, or a global — gives outside code a way to read or mutate the payload without going through enter(), silently bypassing the lock.
How to fix it
- Copy the payload out with ':=:' instead of a reference assignment - the variable then holds a copy of the value, never the lock's own object (the [EXAMPLE] section below shows the reference assignment, and the copy)
- To change the payload, copy, merge or replace INTO 'lockedItem' (':=:', ':~:', ':^:'), which updates the lock's object in place
- If you need to expose a derived value (not the whole payload), compute the derived value inside the callback and copy that out - the length of a locked List, say, rather than the List
Common causes
- Trying to return the locked value from a method by assigning it to the return variable instead of copying it into the variable.
- Capturing a class field and assigning lockedItem to it — the captured reference outlives the callback.
- Mistaking ':=' (reference assignment) for ':=:' (copy). The operators look similar but have very different semantics.
[OPERATORS ON 'lockedItem' INSIDE A MutexKey BODY] Rejected (alias-creating):
- ':=' reference assignment
- '=' also assignment
- ':' declaration assignment
- ':=?' assign-if-unset
Allowed (in-place mutation, no new reference):
- ':=:' copy
- ':~:' merge (fills what is unset; never overwrites a set value)
- ':^:' replace
- '+=', '-=', '*=', '/=' compound arithmetic
- Direct method calls on lockedItem
- Read access (field reads, calls to pure methods)
See also
Examples
Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.
Fails with E08258: data inside a MutexLock must be purely contained — assigning 'lockedItem' creates an external reference; use ':=:' (copy), ':~:' (merge), or ':^:' (replace) instead
#!ek9 defines module error.e08258.bad defines class LeakingHolder lockedName as MutexLock of String: MutexLock("initial") //After this method returns, 'escaped' holds a reference to the same //String object that lives inside lockedName — outside the lock. expose() <- escaped as String: String() accessKey <- (escaped) is MutexKey of String as function escaped := lockedItem require lockedName.enter(accessKey) default operator ?
Fixed: Copy the locked value out with :=:
#!ek9 defines module error.e08258.good defines class CopyingHolder lockedName as MutexLock of String: MutexLock("initial") reveal() <- copied as String: String() accessKey <- (copied) is MutexKey of String as function copied :=: lockedItem require lockedName.enter(accessKey) default operator ?