Mutability Coercion on Copy

Claro's builtin copy(...) function supports the ability to coerce the mutability of the data being copied. This is primarily a matter of convenience to, in as many cases as possible, avoid Claro programmers to needing to manually write custom copy implementations.

In order to convey that a mutability coercion is being requested, the return type of the copy(...) call simply needs to be constrained to some variant of the original value's type with mutability annotations updated as desired. Claro will automatically codegen the appropriate logic to perform the requested copying + coercion. Note that this feature relies on compile-time knowledge to ensure that any coercions would not actually invalidate any language semantics or violate type system rules.

In the below example, a mut [[int]] is copied, with the type simultaneously coerced to [mut [int]]:

Fig 1:


var original = mut [[1, 2, 3], [4, 5], [6]];
var coercedCopy: [mut [int]] = copy(original);
type(coercedCopy);
print("Elements of `coercedCopy` match `original`?: {checkElementsMatch(original, coercedCopy)}\n");

# Now demonstrate that the lists are now independent.
print("Before mutation -");
print("original:    {original}");
print("coercedCopy: {coercedCopy}\n");

original[0]       = [-11111111];
coercedCopy[0][0] = -22222222;  # <-- Outer list is now immutable, so modifying now mutable inner list.

print("After mutation -");
print("original:    {original}");
print("coercedCopy: {coercedCopy}");

function checkElementsMatch(original: mut [[int]], coercedCopy: [mut [int]]) -> boolean {
  # ...
  var i = 0;
  while (i < len(original)) {
    var j = 0;
    while (j < len(original[i])) {
      if (original[i][j] != coercedCopy[i][j]) {
        return false;
      }
      ++j;
    }
    ++i;
  }
  return true;
}

Output:

[mut [int]]
Elements of `coercedCopy` match `original`?: true

Before mutation -
original:    mut [[1, 2, 3], [4, 5], [6]]
coercedCopy: [mut [1, 2, 3], mut [4, 5], mut [6]]

After mutation -
original:    mut [[-11111111], [4, 5], [6]]
coercedCopy: [mut [-22222222, 2, 3], mut [4, 5], mut [6]]

Mutability Coercion Can Apply to Type Parameters of a User Defined Type

It's worth noting explicitly that Claro's newtype declarations statically encode the mutability any collections they happen to wrap. Claro's builtin copy(...) cannot be used to invalidate these explicit mutability declarations, for example:

Fig 2:


# There's nothing that can possibly be done to make Foo<T> wrap a mutable list.
newtype Foo<T> : [T]

However, parameterized User Defined Types may accept any concrete type in the place of the generic type parameter, and Claro's builtin copy(...) function can be used to do mutability coercion on these values.

The below example demonstrates setting the concrete type T = mut tuple<string, int> meaning that Foo<T> originally wraps the type [mut tuple<string, int>]. Then, upon copying the original value, the type is coerced to T = tuple<string, int> resulting in Foo<T> wrapping the deeply immutable type [tuple<string, int>]:

Fig 3:


newtype Foo<T> : [T]

var original: Foo<mut tuple<string, int>> = Foo([mut ("original", 1)]);
var coercedCopy: Foo<tuple<string, int>> = copy(original);

unwrap(original)[0][0] = "UPDATED";
unwrap(original)[0][1] = 2;

print("original:    {original}");
print("coercedCopy: {coercedCopy}");

Output:

original:    Foo([mut (UPDATED, 2)])
coercedCopy: Foo([(original, 1)])