If a parameter cannot be reassigned, is it read-only? How do aliasing and copying work in EK9?

← Functions and Methods · Ref: Q2000

EK9 keeps reference semantics: a variable is a BINDING to an object, and '<-' or '=' to another variable binds a second name to the SAME object. After 'shared <- original', 'shared++' changes what 'original' shows. For an independent value, construct one: 'independent <- Integer(original)' (or copy into an existing object with ':=:').

An incoming parameter's BINDING cannot be reassigned - 'counter := 0' on a parameter is E08110. That is all E08110 says. The parameter is NOT read-only: in non-pure code the object it refers to can be mutated in place ('counter += 1', 'items.add(x)') and the caller sees the change; it can also be aliased and the alias reassigned.

Read-only is what 'pure' provides. A pure function or method cannot mutate or reassign (E08100, E08120), and pure code may call only pure code (E08130), so the guarantee carries through everything it calls: 'doubled() as pure' may read its parameter but 'counter += 1' inside it is E08120.

Copying: ':=:' copies a value INTO an existing object. Each type copies at its own level - a value copies its value, a List copies its element references - and the generated 'default operator :=:' is SHALLOW: each field of the target then refers to the source's object. A deep or selective copy is an 'operator :=:' you write (see Q2100).

See Q737 for reassignment in pure code and Q905 for mutating operators and aliasing.

Example

defines module qa.functions.bindingsandobjects

  defines function

    bump()
      -> counter as Integer
      counter += 1

    doubled() as pure
      -> counter as Integer
      <- rtn as Integer: Integer()
      rtn: counter * 2

  defines program

    BindingsAndObjects()
      stdout <- Stdout()

      original <- 1
      shared <- original
      shared++
      stdout.println(`after shared++ original=${original} shared=${shared}`)

      independent <- Integer(original)
      independent++
      stdout.println(`after independent++ original=${original} independent=${independent}`)

      bump(original)
      stdout.println(`after bump(original) original=${original}`)

      stdout.println(`doubled(original)=${doubled(original)} original=${original}`)

Common mistakes

E08110 — A parameter's binding cannot be reassigned. Mutate the caller's object in place with '+=', or work on a local copy ('working <- Integer(counter)'). See ek9 -h E08110 for details.

Incorrect:

      counter := counter + 1

Correct:

      counter += 1

E08120 — A pure function may not mutate anything - that is what makes its parameters read-only. Compute into the returning variable instead. See ek9 -h E08120 for details.

Incorrect:

      counter += 1

Correct:

      rtn: counter * 2
Other ways to ask this
  • Does E08110 mean my function's parameters are read-only?
  • Can a function change an object the caller passed in?
  • Why does b++ also change a after b <- a?
  • How do I get an independent copy of a value instead of an alias?
  • What makes a parameter really read-only in EK9?

Coming from another language?

Java: parameters are references passed by value - reassigning one is legal but invisible to the caller, while mutating the object is visible; 'final' stops the reassignment only. Python: the same, with no 'final' at all. Kotlin: parameters are 'val' (no reassignment) but the object can still be mutated. C++: 'const T&' gives read-only access. EK9: the binding of a parameter is never reassignable (E08110); 'pure' is the read-only guarantee, enforced transitively.

Keywords: reassignment, alias, pure, E08120, binding, copy, aliasing, read-only, mutation, parameter, reference, E08110