E08257: MutexLock(...) argument must be a fresh value — a literal or a constructor call (MutexLock(List() of Item) builds an empty payload; MutexLock(String(existing)) copies, but only for a type with a copy constructor - List and Dict have none); not a reference to existing state

Category: E08xxx Pre-IR checks (flow analysis) · Reported in phase: FULL_RESOLUTION · Classification: MUTEX_LOCK_ARG_NOT_FRESH · All error codes

What went wrong

The argument passed to a MutexLock(...) constructor is not a fresh value. The argument becomes the lock's protected payload — if the caller still holds a reference to it, the caller can mutate the payload outside the lock, bypassing enter() entirely.

To close this "inbound encapsulation" loophole, the argument must be one of:

  • A literal: MutexLock(0), MutexLock(""), MutexLock(true)
  • A constructor call: MutexLock(List() of Item), MutexLock(MyClass())
  • A copy made by a copy constructor: MutexLock(String(stash)) - note that List and Dict have none ('List(existing)' is a List holding 'existing' as its one item, not a copy of it)

A bare reference to existing state (identifier, field access, non-constructor method call result) is rejected.

Rationale

EK9's compile-time data-race detection guarantees that mutations of the locked payload happen only through enter() callbacks. That guarantee is only meaningful if the caller cannot retain an alias to the payload. By requiring the argument to be fresh (literal, freshly constructed, or explicitly copied), the compiler can be sure no external reference to the payload exists at the moment the MutexLock is built.

How to fix it

  1. If you have an existing value you want to lock around, give the lock a copy made by the type's copy constructor (the [EXAMPLE] section below: 'MutexLock(String(stash))' rather than 'MutexLock(stash)')
  2. If you want to start with an empty payload, give it a freshly constructed empty value ('MutexLock(List() of Item)') and add to it through enter()
  3. Move the construction earlier so the value used as the argument is itself freshly constructed in scope.

Common causes

  • Passing a parameter directly to MutexLock: MutexLock(arg)
  • Passing a field of 'this': MutexLock(this.field)
  • Passing a method result that returns a held reference

See also

  • E08254: MutexLock cannot appear in a generic type parameter
  • E08256: MutexLock construction outside field initialiser

Examples

Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.

Fails with E08257: MutexLock(...) argument must be a fresh value — a literal or a constructor call (MutexLock(List() of Item) builds an empty payload; MutexLock(String(existing)) copies, but only for a type with a copy constructor - List and Dict have none); not a reference to existing state

#!ek9
defines module error.e08257.bad

  defines class

    BadLeakedRef
      stash as String: String("retained-by-caller")

      //Bad: 'stash' is retained by this class as a field; the MutexLock's
      //payload is therefore aliased outside the lock.
      lockedName as MutexLock of String: MutexLock(stash)

      default operator ?

Fixed: Give the lock a fresh value - a literal or a constructor call (a copy)

#!ek9
defines module error.e08257.good

  defines class

    GoodFreshCopy
      stash as String: String("retained-by-caller")
      lockedName as MutexLock of String: MutexLock(String(stash))

      default operator ?