Reference Parameters — Design

Issue: #1243, split out of #1186.

Why

Every argument is passed by value, so no function can change a caller's local:

a := 1; b := 2;
Swap(a, b);   # cannot exchange a and b

Code worked around this with holder objects (IntRef, a Ref<T> cell) or by returning a record. Research code wants it for the small cases: return a second result (q := Divmod(a, b, ref r)), update an accumulator in a helper, or swap two variables. A holder in each of those is noise.

Decisions

Syntax: ref at the parameter and at the call

function : Swap(ref a : Int, ref b : Int) ~ Nil {
  t := a; a := b; b := t;
}

Swap(ref x, ref y);
q := Divmod(17, 5, ref r);

Semantics: copy in, copy out

A ref argument's value goes into the call, and the callee's final value comes back into the caller's variable after the statement. That is how Swift's inout is defined, and the observable behaviour matches true aliasing for every program that does not pass one variable twice or read it again inside the same statement.

This is done by the parser (ParseReferenceArgument, TakeTrailingStatements).

What can be passed

Where a call with ref arguments may stand

The copy back runs after the statement. A call with ref arguments must therefore be a statement of its own or the right side of an assignment or declaration. Inside an if or while condition, a for header or a return, the copy would come at the wrong time or never, so those are compile errors.

Overloads, libraries

Found on the way

An array literal with a subscripted element, [a[i], a[2]], typed the element by the array's dimension, not the element's, and was rejected ("Invalid class type"). It is a single value now (AnalyzeDynamicArray). This was a #1245 gap, and the ref cell of a ref parameter passed on hit it.

Tests

ref_parameters.obs covers:

All of it runs in three modes. The bad_ref_*.obs files cover: