copy_shared: a function builds a new copy because the value is still shared

March updates a list or tree in place when the function rebuilding it holds the only reference to it: List.map over a list nobody else holds rewrites its cells instead of allocating new ones (see the memory model). When something else still holds the value, the function quietly allocates a fresh copy instead. The program is still correct, only slower, with more memory.

This warning comes only from march --why-copies FILE (or --why-copies-json). It names the call that copies, the value it could not reuse, and why the value was still shared:

  • it is used again afterwards in the same function;
  • it is also passed to the enclosing call, which holds it until it runs;
  • a closure captured it, and a closure keeps its own reference.

A program that triggers it

mod Main do
  needs IO.Console

  fn inc_all(xs : List(Int)) : List(Int) do
    let ys = List.map(xs, fn x -> x + 1)
    let n = List.length(xs)
    List.append(ys, [n])
  end

  fn main(c : Cap(IO.Console)) do
    println(int_to_string(List.length(inc_all([1, 2, 3]))))
  end
end

xs is used by List.length after List.map runs, so List.map cannot reuse its cells.

The fix

Make the call the value’s last use. Here, take the length first:

mod Main do
  needs IO.Console

  fn inc_all(xs : List(Int)) : List(Int) do
    let n = List.length(xs)
    let ys = List.map(xs, fn x -> x + 1)
    List.append(ys, [n])
  end

  fn main(c : Cap(IO.Console)) do
    println(int_to_string(List.length(inc_all([1, 2, 3]))))
  end
end

When moving the later let is safe (it is the only later use, it calls only functions with no side effects, and it uses nothing bound in between), the warning carries this move as a fix that forge fix can apply. When the value is used several more times, or a closure captured it, there is no mechanical fix: decide whether the copy matters, and restructure if it does.

See it in the error tour