In-place Updates
In-place updates do not produce observable side effects, but they provide a way to update an array efficiently — the cost is proportional to the size of the value(s) being written, not the size of the full array.
The a with [i] = v construct (and its derived forms) performs an
in-place update. The compiler verifies that the original array a is
not used on any execution path following the update, and that no
alias of a is used either. Most language constructs produce fresh
arrays with no aliases; slicing is the main exception — a slice
aliases its source.
A function parameter may be marked consuming by prefixing its
type with *. A return type may be marked alias-free the same
way. For example:
def modify(a: *[]i32, i: i32, x: i32) *[]i32 =
a with [i] = a[i] + x
A parameter that is not consuming is called observing. The * in
a: *[]i32 means modify takes ownership of a; no caller may
reference a (or any alias of it) after the call. This is what
permits the with expression to update in place. After a call
modify(a, i, x), neither a nor any alias of a may be used on
any subsequent execution path.
If a * appears anywhere inside a tuple parameter type, the whole
parameter is considered consuming:
def consumes_both(p: (*[]i32, []i32)) i32 = ???
This is usually not desirable. Prefer separate parameters:
def consumes_first_arg(a: *[]i32, b: []i32) i32 = ???
For bulk in-place updates with multiple values, use the scatter
function from the prelude.
Alias Analysis
The rules used to determine aliasing are intuitive in the intra-
procedural case. Aliases are associated with entire arrays. Aliases
of a record or tuple are tracked for each element, not for the record
or tuple itself. Most constructs produce fresh arrays with no
aliases; the main exceptions are if, loop, function calls, and
variable references.
After a binding let a = b, which simply assigns a new name to an
existing variable, a aliases b. Similarly for record projections
and pattern bindings.
The result of an if aliases the union of the aliases of its
branches.
The result of a loop aliases the initial values as well as any
aliases that the merge parameters may assume at the end of an
iteration, computed to a fixed point.
The aliases of a value returned from a function depend on whether the
return value is declared alias-free (with *). If it is, the value
has no aliases. Otherwise, it aliases all arguments passed for
non-consumed parameters.
In-place Updates and Higher-order Functions
Consumption interacts inflexibly with higher-order functions: the language cannot control how many times a function argument is applied, or to what, so it is not safe to pass a function that consumes its argument. Two conservative rules govern this interaction:
- In the expression
let p = e1 in …, if any in-place update takes place insidee1, the value bound bypmust not be or contain a function. - A function that consumes one of its arguments may not be passed as a higher-order argument to another function.