Nanyx

Memory

Scoped mutation with the memory context

Mutation in Nanyx is available but bounded. Mutable values require the $Memory context, and memory { ... } is the helper that creates and scopes that context. This allows internal mutation while keeping external interfaces pure.

$Memory is a nominal context owned by the standard library, so memory { ... } is the only way to obtain an instance of it; user code cannot construct one with use.

Local mutation

def sort: [Ord(a)] list(a) -> list(a) = { l ->
  memory {
    def arr = MArray.of(l)
    MArray.sort(arr)
    MArray.toList(arr)
  }
}

Mutability requires context

mut e creates a mutable cell holding the value of e, and := replaces its contents. Reading a cell is implicit: using the cell’s name where a plain value is expected reads the current contents.

def main = {
  def x = mut 0 -- Error: requires $Memory context
}

def main = {
  memory {
    def x = mut 0 -- OK
    x := x + 1
    println("{x}") -- prints 1
  }
}

The type of a cell records that it requires $Memory, which is what prevents it from leaking out of the scope (see below). := is the only assignment operator in the language.

Mutable collections

Mutable collections such as MList are ordinary values with attached operators that update in place. += here is an attached definition on MList, not a second kind of assignment.

memory {
  def items: MList(int) = []
  items += 1
  items += 2
  MList.toList(items) -- [1, 2]
}

Values cannot escape memory scope

Values that require the $Memory context cannot be returned out of a memory { ... } scope.

def bad = {
  memory {
    def arr = MArray.of([1, 2, 3])
    arr -- Error: value requires $Memory context and cannot escape this scope
  }
}

To return data, convert it to an immutable value before leaving the scope:

def good = {
  memory {
    def arr = MArray.of([1, 2, 3])
    MArray.toList(arr) -- OK
  }
}

This is how Nanyx performs escape analysis. Instead of a separate escape-analysis pass, the compiler tracks context requirements in types. If a value’s type still needs $Memory, it can only be used where $Memory is available, so it cannot leak into pure code.

For the detailed proposal, see Work in progress: Context-qualified types.

For the general model behind this, see Contexts: Contexts Are Part of the Type.

No global mutable state

Because memory is scoped, global mutable state is effectively impossible. Top-level mut values will fail to compile because a top-level definition cannot be inside a context.