Copy
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| Written | What arrives | A change through it reaches the original? |
|---|---|---|
var end = start; | a copy | no |
func F(point: Point) | a copy | no — and the parameter is read-only |
func F(point: &Point) | a borrow, to read | it cannot change anything |
func F(point: &var Point) | a borrow, to change | yes |
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.
// 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
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.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.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
- Write
func Nudge(point: &var Point)that adds 10 tox, call it on avarpoint, and compare the result withNudged. - Make an array of two
Points, copy it into avar, and changexof the copy's first point. Print the first point of both arrays. - Change the body of
Nudgedtopoint.x += 10; return point;and read the compiler's help.
Learn more
- Move — handing a value on without copying it
- Reference and Mutable reference — sharing one value instead of copying it
- Struct and Array — the values copied in this lesson