Ownership · Lesson 11.2

Copy

Source
See that a by-value copy is a separate value: changing it never changes its source.
You'll need: Struct, Array, Function

Every time a value is used by value — bound to a new name, passed to a parameter that is not a reference, returned, or assigned over another value — what arrives is a copy. From that moment the two are separate: changing one never shows up in the other.

That is the opposite of the Reference lessons, where a borrow shares one value instead of making a second. Most of the time a copy is exactly what you want, and you have been relying on it since Part 1 without a name for it. This lesson gives it the name, because the rest of the part is about values for which a plain copy is not right.

Nothing to write

A struct needs no extra code to be copyable. The compiler copies a struct by copying each field, and an array by copying each element, as long as every part can itself be copied:

struct Point {
    x: int32;
    y: int32;
}

Point is two int32s, and an int32 copies, so a Point copies.

Binding a new name

end starts as a copy of start, then goes its own way:

let start = Point { x: 1, y: 2 };
var end = start;
end.y = 99;

end is now (1, 99), and start is still (1, 2). There are two points in memory, not one point with two names.

Passing and returning

A parameter that is not a reference receives its own copy. A parameter cannot be changed, so Nudged copies it once more into a var, changes that, and returns it:

func Nudged(point: Point) -> Point {
    var result = point;
    result.x += 10;
    return result;
}
let nudged = Nudged(start);

nudged is (11, 2). The caller's start is never touched — the function only ever saw copies of it.

Arrays copy too

An array is a value, so assigning it copies the whole array, element by element:

let scores = [3, 5, 8];
var adjusted = scores;
adjusted[0] = 100;

scores[0] is still 3. If you come from a language where an array variable is a pointer to shared storage, this is the place to slow down: in Rux, adjusted is a second array.

Assigning over a value

Assigning to an existing variable copies again and replaces what was there:

end = start;

end was (1, 99); now it is a fresh copy of start, (1, 2).

Copy or borrow?

flowchart LR
    subgraph copy["By value: let end = start"]
        s1["start (1, 2)"]
        e1["end (1, 2)<br/>a second Point"]
        s1 -- "copied into" --> e1
    end
    subgraph borrow["By reference: let view: &Point = start"]
        s2["start (1, 2)"]
        v2["view"] -- "refers to" --> s2
    end
WrittenWhat arrivesA change through it reaches the original?
var end = start;a copyno
func F(point: Point)a copyno — and the parameter is read-only
func F(point: &Point)a borrow, to readit cannot change anything
func F(point: &var Point)a borrow, to changeyes

A copy is cheap for small values like a Point, and it is always safe: nothing you do to a copy can surprise the code holding the original. The next lesson covers the other way to hand a value on — moving it, so that only one remains.

The program

The whole lesson is one package in the Examples repository. Its comments explain every step.

Src/Main.rux
// Every time a value is used by value — bound to a new name, passed to a parameter that is not a
// reference, returned, or assigned over another value — what arrives is a copy. From that moment
// the two are separate: changing one never shows up in the other.
//
// Nothing has to be written to make a struct copyable. The compiler copies a struct by copying
// each field, and an array by copying each element, as long as every part can itself be copied.
// The Reference lessons were the opposite case: a borrow shares one value instead of making a
// second one.
import Io::PrintLine;

struct Point {
    x: int32;
    y: int32;
}

// `point` is this function's own copy. A parameter cannot be changed, so it is copied once more
// into a `var`, which is changed and returned. The caller's point is never touched.
func Nudged(point: Point) -> Point {
    var result = point;
    result.x += 10;
    return result;
}

func Main() -> int {
    // Binding: `end` starts as a copy of `start`, then goes its own way.
    let start = Point { x: 1, y: 2 };
    var end = start;
    end.y = 99;
    PrintLine("start ({}, {})   end ({}, {})", start.x, start.y, end.x, end.y);

    // Passing and returning: the function worked on a copy.
    let nudged = Nudged(start);
    PrintLine("start ({}, {})   nudged ({}, {})", start.x, start.y, nudged.x, nudged.y);

    // An array is a value too, so the whole array is copied, element by element.
    let scores = [3, 5, 8];
    var adjusted = scores;
    adjusted[0] = 100;
    PrintLine("scores[0] {}   adjusted[0] {}", scores[0], adjusted[0]);

    // Assigning over an existing value copies again and replaces what was there.
    end = start;
    PrintLine("end ({}, {}) after end = start", end.x, end.y);
    return 0;
}

Run it

cd Examples/Ownership/Copy
rux run
start (1, 2)   end (1, 99)
start (1, 2)   nudged (11, 2)
scores[0] 3   adjusted[0] 100
end (1, 2) after end = start

Common mistakes

Expecting a change to reach the original.
There is no error to warn you here, which is what makes it a mistake. var end = start; end.y = 99; changes end only. If the caller's value must change, the function needs a &var parameter, as in Mutable reference.
Changing a by-value parameter.
func Nudged(point: Point) -> Point { point.x += 10; return point; } fails with error: cannot modify parameter 'point'. The compiler's help names both fixes: take point as &var Point to change the caller's value, or move it into a var local — or, as Nudged does, copy it into one.
Changing a let copy.
let end = start; end.y = 99; fails with error: cannot modify immutable variable 'end'. The copy is a new variable with its own mutability, and a let never changes. Declare it with var.

Try it yourself

  1. Write func Nudge(point: &var Point) that adds 10 to x, call it on a var point, and compare the result with Nudged.
  2. Make an array of two Points, copy it into a var, and change x of the copy's first point. Print the first point of both arrays.
  3. Change the body of Nudged to point.x += 10; return point; and read the compiler's help.

Learn more