Skip to content
Learn Rust
Theme

Copy or reference? Telling them apart

Ownership · Lesson 16

Every language has a quiet rule for whether b = a or a for loop gives you your own copy or a second name for the same thing. Rust has the same three outcomes (a copy, a shared reference, a move), but it writes which one you get into the code. This lesson gives you tests to find the answer in your own language first.

Coming from Gofor _, p := range people hands you a copy of each element, so p.Age++ changes nothing and you write people[i].Age++ instead. In Rust the loop says which you get: for p in &people reads each element, for p in &mut people changes it. range over an array also copies the whole array first, and over a slice it does not.

Coming from Kotlinfor (p in people) gives you the object itself, so p.age++ changes it, but the loop variable is always a val, so age++ on a number is an error. Rust spells out the object case: for p in &mut people changes each item, for p in &people reads it.

Coming from PythonEvery name in Python leads to an object, so for p in people: p.age += 1 changes the objects, while for n in nums: n += 1 changes nothing (an int cannot change; += makes a new one). Rust shows it: for p in &mut people changes the items, and for mut n in nums.iter().copied() is a copy.

Coming from TypeScriptfor (const p of people) p.age++ changes the objects, because const fixes the name, not the object, and for (let n of nums) n++ changes nothing, because numbers are copied. Rust writes the difference in the loop: &mut people for the objects themselves, .iter().copied() for copies.

Coming from Javafor (Person p : people) copies the reference (Java passes references by value), so p.age++ reaches the object but p = other changes only the loop variable. An int loop variable is a plain copy. Rust makes the object case visible: &mut people.

Coming from C#foreach over a class list gives you the object, but over a struct list it gives you a read-only copy, and p.Age++ is error CS1654. Rust has no struct/class split: every type holds its value, and &/&mut in the loop say whether you see the element itself.

Coming from CA C loop that copies people[i] into a local struct changes only the copy, and &people[i] reaches the original. Rust has the same two choices, .iter().copied() and &mut people, and the compiler checks the pointer.

Coming from C++for (auto p : v) copies each element and for (auto& p : v) does not: one character apart. In Rust the loop variable of &mut v is visibly a &mut T, and p += 1 on it is a compile error until you write *p.

The quick comparison

Rustsrc/main.rsRun ↗
fn main() {
let mut ages = vec![30, 40, 50];
// A copy: `age` is its own number, so the Vec does not change.
for mut age in ages.iter().copied() {
age += 1;
println!("the copy is now {age}");
}
println!("ages after the copy loop: {ages:?}");
// The elements themselves: `&mut` in the code says so.
for age in &mut ages {
*age += 1;
}
println!("ages after the in-place loop: {ages:?}");
}
$ cargo run
the copy is now 31
the copy is now 41
the copy is now 51
ages after the copy loop:     [30, 40, 50]
ages after the in-place loop: [31, 41, 51]
package main
import "fmt"
func main() {
ages := []int{30, 40, 50}
// A copy: range gives you each element's value, not the element.
for _, age := range ages {
age++
fmt.Println("the copy is now", age)
}
fmt.Println("ages after the copy loop: ", ages)
// The elements themselves: index into the slice.
for i := range ages {
ages[i]++
}
fmt.Println("ages after the in-place loop:", ages)
}
$ go run main.go
the copy is now 31
the copy is now 41
the copy is now 51
ages after the copy loop:     [30 40 50]
ages after the in-place loop: [31 41 51]
fun main() {
val ages = mutableListOf(30, 40, 50)
// A copy: the loop variable is a val holding its own number.
for (age in ages) {
val copy = age + 1
println("the copy is now $copy")
}
println("ages after the copy loop: $ages")
// The elements themselves: assign through the index.
for (i in ages.indices) {
ages[i]++
}
println("ages after the in-place loop: $ages")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
the copy is now 31
the copy is now 41
the copy is now 51
ages after the copy loop:     [30, 40, 50]
ages after the in-place loop: [31, 41, 51]
ages = [30, 40, 50]
# A copy: `age` is a name for an int, and += builds a new int.
for age in ages:
age += 1
print("the copy is now", age)
print("ages after the copy loop: ", ages)
# The elements themselves: assign into the list.
for i in range(len(ages)):
ages[i] += 1
print("ages after the in-place loop:", ages)
$ python3 main.py
the copy is now 31
the copy is now 41
the copy is now 51
ages after the copy loop:     [30, 40, 50]
ages after the in-place loop: [31, 41, 51]
const ages = [30, 40, 50];
// A copy: `age` holds its own number.
for (let age of ages) {
age += 1;
console.log("the copy is now", age);
}
console.log("ages after the copy loop: ", ages);
// The elements themselves: assign into the array.
for (let i = 0; i < ages.length; i++) {
ages[i] += 1;
}
console.log("ages after the in-place loop:", ages);
$ tsc --strict --module nodenext --types node main.ts && node main.js
the copy is now 31
the copy is now 41
the copy is now 51
ages after the copy loop:     [ 30, 40, 50 ]
ages after the in-place loop: [ 31, 41, 51 ]
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] ages = {30, 40, 50};
// A copy: an int loop variable holds its own number.
for (int age : ages) {
age++;
System.out.println("the copy is now " + age);
}
System.out.println("ages after the copy loop: " + Arrays.toString(ages));
// The elements themselves: index into the array.
for (int i = 0; i < ages.length; i++) {
ages[i]++;
}
System.out.println("ages after the in-place loop: " + Arrays.toString(ages));
}
}
$ javac Main.java && java Main
the copy is now 31
the copy is now 41
the copy is now 51
ages after the copy loop:     [30, 40, 50]
ages after the in-place loop: [31, 41, 51]
using System;
int[] ages = { 30, 40, 50 };
// A copy: the foreach variable is read-only, so copy it to change it.
foreach (var age in ages)
{
var copy = age + 1;
Console.WriteLine($"the copy is now {copy}");
}
Console.WriteLine($"ages after the copy loop: [{string.Join(", ", ages)}]");
// The elements themselves: assign through the index.
for (int i = 0; i < ages.Length; i++)
{
ages[i]++;
}
Console.WriteLine($"ages after the in-place loop: [{string.Join(", ", ages)}]");
$ dotnet run
the copy is now 31
the copy is now 41
the copy is now 51
ages after the copy loop:     [30, 40, 50]
ages after the in-place loop: [31, 41, 51]
#include <stdio.h>
int main(void) {
int ages[3] = {30, 40, 50};
// A copy: `age` is a new variable holding the element's value.
for (int i = 0; i < 3; i++) {
int age = ages[i];
age++;
printf("the copy is now %d\n", age);
}
printf("ages after the copy loop: [%d, %d, %d]\n", ages[0], ages[1], ages[2]);
// The elements themselves: a pointer to each element.
for (int i = 0; i < 3; i++) {
int *age = &ages[i];
(*age)++;
}
printf("ages after the in-place loop: [%d, %d, %d]\n", ages[0], ages[1], ages[2]);
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main
the copy is now 31
the copy is now 41
the copy is now 51
ages after the copy loop:     [30, 40, 50]
ages after the in-place loop: [31, 41, 51]
#include <iostream>
#include <vector>
void print(const char *label, const std::vector<int> &v) {
std::cout << label << "[";
for (size_t i = 0; i < v.size(); i++) {
std::cout << (i ? ", " : "") << v[i];
}
std::cout << "]\n";
}
int main() {
std::vector<int> ages{30, 40, 50};
// A copy: `auto` makes age a new int.
for (auto age : ages) {
age++;
std::cout << "the copy is now " << age << "\n";
}
print("ages after the copy loop: ", ages);
// The elements themselves: `auto&` is a reference to each one.
for (auto &age : ages) {
age++;
}
print("ages after the in-place loop: ", ages);
return 0;
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main
the copy is now 31
the copy is now 41
the copy is now 51
ages after the copy loop:     [30, 40, 50]
ages after the in-place loop: [31, 41, 51]

What's different

  • In most languages assignment, passing an argument and a loop variable copy the variable's bytes. The question is whether those bytes are the data or an arrow to it. (C++ is the exception: it runs the type's copy constructor.)
  • In Rust the code shows it. A plain name is a value (copied if the type is Copy, moved if not). A & or &mut is an arrow.
  • for x in v moves the Vec, for x in &v borrows each item, and for x in &mut v borrows each item so you can change it with *x.
  • .clone() is the copy you ask for by name. Types that are Copy (numbers, bool, char, &T) copy without it.
Gotcha

Changing the loop variable

Looks fine: in C it runs fine; in Go, Python, TypeScript, Java and C++ it runs but gives a wrong answer. Rust won't compile it.

Broken in Go. Rust catches it.Kotlin refuses this too. Same idea, different words.Broken in Python. Rust catches it.Broken in TypeScript. Rust catches it.Broken in Java. Rust catches it.C# refuses this too. Same idea, different words.Works in C. In Rust it's just a rule.Broken in C++. Rust catches it.
Rustsrc/main.rs✗ won't compile
fn main() {
let mut ages = vec![30, 40, 50];
for age in &mut ages {
age += 1;
}
println!("{ages:?}");
}
package main
import "fmt"
func main() {
ages := []int{30, 40, 50}
for _, age := range ages {
age++
}
fmt.Println(ages)
}
$ go run main.go! runs · wrong answer
[30 40 50]
Kotlin✗ refuses too

Kotlin refuses: the loop variable of a for is a val, so age++ is "'val' cannot be reassigned". You change the list through its index, ages[i]++.

fun main() {
val ages = mutableListOf(30, 40, 50)
for (age in ages) {
age++
}
println(ages)
}
Main.kt:4:9: error: 'val' cannot be reassigned.
        age++
        ^^^
ages = [30, 40, 50]
for age in ages:
age += 1
print(ages)
$ python3 main.py! runs · wrong answer
[30, 40, 50]
const ages = [30, 40, 50];
for (let age of ages) {
age += 1;
}
console.log(ages);
$ tsc --strict --module nodenext --types node main.ts && node main.js! runs · wrong answer
[ 30, 40, 50 ]
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] ages = {30, 40, 50};
for (int age : ages) {
age++;
}
System.out.println(Arrays.toString(ages));
}
}
$ javac Main.java && java Main! runs · wrong answer
[30, 40, 50]
C#✗ refuses too

C# refuses with CS1656: a foreach variable is read-only. You change the array through its index, ages[i]++.

using System;
int[] ages = { 30, 40, 50 };
foreach (var age in ages)
{
age++;
}
Console.WriteLine($"[{string.Join(", ", ages)}]");
Program.cs(6,5): error CS1656: Cannot assign to 'age' because it is a 'foreach iteration variable'
#include <stdio.h>
int main(void) {
int ages[3] = {30, 40, 50};
for (int i = 0; i < 3; i++) {
int age = ages[i];
age += 1;
}
printf("[%d, %d, %d]\n", ages[0], ages[1], ages[2]);
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main✓ runs
[30, 40, 50]
#include <iostream>
#include <vector>
int main() {
std::vector<int> ages{30, 40, 50};
for (auto age : ages) {
age += 1;
}
std::cout << "[" << ages[0] << ", " << ages[1] << ", " << ages[2] << "]\n";
return 0;
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main! runs · wrong answer
[30, 40, 50]
What does the compiler say? Guess first, then open.
$ cargo buildWhat is E0368?
error[E0368]: binary assignment operation `+=` cannot be applied to type `&mut {integer}`
 --> src/main.rs:4:9
  |
4 |         age += 1;
  |         ---^^^^^
  |         |
  |         cannot use `+=` on type `&mut {integer}`
  |
help: `+=` can be used on `{integer}` if you dereference the left-hand side
  |
4 |         *age += 1;
  |         +
What happens in Go

Go runs this and prints [30 40 50]. range gives you a copy of each element, so age++ changes the copy and the slice is untouched. The compiler does not complain. With []Person the same happens to p.Age++; with []*Person it reaches the Person, because the copy is of the pointer. You write ages[i]++.

What happens in Kotlin

Kotlin refuses: the loop variable of a for is a val, so age++ is "'val' cannot be reassigned". You change the list through its index, ages[i]++.

What happens in Python

Python prints [30, 40, 50]. age is a name for each int in turn. += on an int builds a new int and points age at it, so the list still holds the old ones.

What happens in TypeScript

TypeScript prints [ 30, 40, 50 ]. age holds its own copy of each number. (With const age the compiler would refuse age += 1.)

What happens in Java

Java prints [30, 40, 50]. An int loop variable is a copy of the element, and Java allows you to change it. With objects the variable is a copy of the reference, so changing a field would reach the object.

What happens in C#

C# refuses with CS1656: a foreach variable is read-only. You change the array through its index, ages[i]++.

What happens in C

C prints [30, 40, 50], and nobody is surprised: C has no for-each loop, so you wrote the copy int age = ages[i] yourself. ages[i]++ or a pointer &ages[i] would change the array. In Rust the loop variable is the part that differs: it is a reference, not a copy you made.

What happens in C++

C++ prints [30, 40, 50]. auto age is a copy of each element, and g++ with -Wall -Wextra says nothing. auto& age would be the element itself.

What Rust sees

for age in &mut ages gives you age: &mut i32, a reference to the element. age += 1 would add to the reference, which is not defined, so the compiler asks for *age += 1.

Why Rust says no

Most of the other languages quietly pick one answer: the loop variable is a copy (or a name for an item that cannot change), so the increment is thrown away. That is fine until you meant to change the list, and the program gives no sign. Rust makes you write *age for the element, or .iter().copied() if you want a copy, so what you wrote says what you meant.

The fix

Compiles and runs.

Rust, fixedsrc/main.rsRun ↗
fn main() {
let mut ages = vec![30, 40, 50];
for age in &mut ages {
*age += 1; // `age` is a `&mut i32`: say `*` to reach the number
}
println!("{ages:?}");
}
$ cargo run
[31, 41, 51]

The toolkit

Every language has moments where you cannot tell whether a loop gave you the item or a copy of it, or whether a function changed your data. This page gives you tests that find the answer in your own language, then shows where Rust writes it down. Outputs come from the site’s runner (Linux, ARM64; Go 1.27.3, Java 25, Python 3.13, Rust 1.99.1).

One question, three answers

Ask one question: if I change this, does anyone else see it? There are three answers.

Answer What the new name holds Who sees a change
A copy its own bytes of data nobody else
A shared reference an arrow to the same data everybody with an arrow
A move (Rust) the data, handed over from the old name nobody: the old name stops working

In most languages b = a, f(a) and for x in xs copy the variable’s bytes (Values, the Stack and the Heap); C++ runs the type’s copy constructor instead, and a std::vector copies its elements. What differs is what the bytes are, the data or an arrow to it. Rust splits “shared” in two: & is a read-only arrow, &mut one that may change the data (one at a time). The type test asks what the bytes are; mutate and look changes a copy and looks at the original.

Where it hides What to ask
b = a, f(a) the data or an arrow? (your language)
for x in xs is x the element or a copy of it? (Rust loops)
a closure the variable or a copy? (Rust)
a method call the object or a copy?
a struct or list that holds things most languages copy the top level only (a shallow copy); C++ containers and Rust .clone() copy what they own (below)
returning or storing a value a value type is copied, an object stays shared

Detectors in your own language

When you are stumped, run this recipe on the thing you are unsure about:

  1. Make b from a the way your code does (b = a, a call, a loop).
  2. Change something inside b: an element or a field. Do not assign a whole new value to b: rebinding only moves b’s arrow, and is the usual cause of “references are passed by value” confusion.
  3. Print a. If it changed, you shared. If it did not, you copied, or the object cannot change (an int, a string).
  4. Ask the language for identity (below): do a and b lead to the same object or address?

Rust writes the answer in the code

Rust has the same outcomes, but you can see which one you got, and the compiler stops the surprising ones. A plain name is a value; & or &mut in front is an arrow. Copy is a trait, a named ability: a Copy type is duplicated by let b = a, any other is moved.

You write You get Afterwards
let b = a; for a Copy type a copy a still works and is separate
let b = a; for String, Vec, most structs a move a cannot be used
let b = &a; a shared reference a can be read, not changed, while b is in use
let b = &mut a; a unique reference (one at a time; see Mutable borrows) a cannot be used while b is in use
let b = a.clone(); a new, separate value a is unchanged
f(a), f(&a), f(&mut a) the same three, for a parameter

The reference rows are the subject of Borrowing. One of each:

Rustsrc/main.rsRun ↗
fn by_value(mut n: i32) -> i32 {
n += 1;
n
}
fn by_ref(n: &i32) -> i32 {
*n + 1
}
fn by_mut(n: &mut i32) {
*n += 1;
}
fn main() {
let mut a = 5;
println!("by_value(a) returns: {}", by_value(a));
println!("after by_value(a): a = {a}");
println!("by_ref(&a) returns: {}", by_ref(&a));
println!("after by_ref(&a): a = {a}");
by_mut(&mut a);
println!("after by_mut(&mut a): a = {a}");
let b = a; // numbers are Copy: b is its own number
a += 100;
println!("after let b = a: a = {a}, b = {b}");
let s = String::from("hi");
let mut t = s.clone(); // an explicit, independent copy
t.push('!');
println!("after s.clone(): s = {s}, t = {t}");
}
$ cargo run
by_value(a) returns:  6
after by_value(a):    a = 5
by_ref(&a) returns:   6
after by_ref(&a):     a = 5
after by_mut(&mut a): a = 6
after let b = a:      a = 106, b = 6
after s.clone():      s = hi, t = hi!

Loops

The way you write a for loop decides the type of x. This prints it for every form with type_name_of_val (&x only lets it look at x). Here .iter() walks by reference, .iter_mut() by &mut and .copied() yields a copy of each item (Iterators comes later):

Rustsrc/main.rsRun ↗
use std::any::type_name_of_val;
fn main() {
let mut v = vec![10];
for x in v.clone() {
println!("for x in v.clone(): x is {}", type_name_of_val(&x));
}
for x in &v {
println!("for x in &v: x is {}", type_name_of_val(&x));
}
for x in &mut v {
println!("for x in &mut v: x is {}", type_name_of_val(&x));
}
for x in v.iter() {
println!("for x in v.iter(): x is {}", type_name_of_val(&x));
}
for x in v.iter_mut() {
println!("for x in v.iter_mut(): x is {}", type_name_of_val(&x));
}
for x in v.iter().copied() {
println!("v.iter().copied(): x is {}", type_name_of_val(&x));
}
for x in v {
println!("for x in v: x is {}", type_name_of_val(&x));
}
}
$ cargo run
for x in v.clone():    x is i32
for x in &v:           x is &i32
for x in &mut v:       x is &mut i32
for x in v.iter():     x is &i32
for x in v.iter_mut(): x is &mut i32
v.iter().copied():     x is i32
for x in v:            x is i32
You write x is Afterwards
for x in &v, v.iter() &i32, a shared reference v unchanged
for x in &mut v, v.iter_mut() &mut i32, a reference you write through with *x v changed
for x in v.iter().copied() i32, a copy v unchanged
for x in v, v.into_iter() i32, taken out of v v is gone

The last row bites people who expect for x in v to behave like range or for...in:

Rustsrc/main.rs✗ won't compile
fn main() {
let ages = vec![30, 40, 50];
for age in ages {
println!("{age}");
}
println!("{} ages", ages.len());
}
$ cargo buildWhat is E0382?
error[E0382]: borrow of moved value: `ages`
 --> src/main.rs:6:25
  |
2 |     let ages = vec![30, 40, 50];
  |         ---- move occurs because `ages` has type `Vec<i32>`, which does not implement the `Copy` trait
3 |     for age in ages {
  |                ---- `ages` moved due to this implicit call to `.into_iter()`
...
6 |     println!("{} ages", ages.len());
  |                         ^^^^ value borrowed here after move
  |
help: consider iterating over a slice of the `Vec<i32>`'s content to avoid moving into the `for` loop
  |
3 |     for age in &ages {
  |                +

  ... (notes pointing into the standard library trimmed)

The compiler names the loop and suggests &ages, the form that behaves like range or for...of.

When x is a &mut, x.age += 1 and x.len() follow the reference for you. Only a bare change to the number itself needs *x += 1; forgetting it is the gotcha on this page. Inside a function that received &mut Vec<T>, the parameter is already the reference, so loop with people.iter_mut().

From your language to Rust

Closures

A closure is a small function you write inline (|| ...; Closures has the details). It borrows the variables it uses, unless its body needs to take one. With move it takes them, which for a Copy type means a copy:

Rustsrc/main.rsRun ↗
fn main() {
let mut shared = 0;
let mut bump = || shared += 1; // borrows `shared`
bump();
bump();
println!("closure without move: shared = {shared}");
let mut owned = 0;
let mut bump_own = move || {
owned += 1; // changes the closure's own copy
owned
};
bump_own();
let inside = bump_own();
println!("closure with move: owned = {owned}, the closure's copy = {inside}");
}
$ cargo run
closure without move: shared = 2
closure with move:    owned = 0, the closure's copy = 2
Rustsrc/main.rs✗ won't compile
fn main() {
let s = String::from("hi");
let f = || drop(s); // the body takes `s`, so the closure takes it
f();
println!("{s}");
}
$ cargo buildWhat is E0382?
error[E0382]: borrow of moved value: `s`
 --> src/main.rs:5:16
  |
2 |     let s = String::from("hi");
  |         - move occurs because `s` has type `String`, which does not implement the `Copy` trait
3 |     let f = || drop(s); // the body takes `s`, so the closure takes it
  |             --      - variable moved due to use in closure
  |             |
  |             value moved into closure here
4 |     f();
5 |     println!("{s}");
  |                ^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let f = || drop(s.clone()); // the body takes `s`, so the closure takes it
  |                      ++++++++

Which types are Copy

Numbers, bool, char, shared references &T, and tuples, arrays and Options of Copy things. String, Vec and Box own heap memory, so they are not, and neither is &mut T. Your own struct is not Copy until you write #[derive(Clone, Copy)], which needs every field to be Copy. The Copy docs list the standard ones, and the compiler tells you the rest (ignore the syntax here; the error is the point):

Rustsrc/main.rs✗ won't compile
// Compiles only for types that are `Copy`.
fn is_copy<T: Copy>(_name: &str) {}
fn main() {
is_copy::<i32>("i32");
is_copy::<String>("String");
is_copy::<Vec<i32>>("Vec<i32>");
is_copy::<&mut i32>("&mut i32");
}
$ cargo buildWhat is E0277?
error[E0277]: the trait bound `String: Copy` is not satisfied
 --> src/main.rs:6:15
  |
6 |     is_copy::<String>("String");
  |               ^^^^^^ the trait `Copy` is not implemented for `String`
  |
note: required by a bound in `is_copy`
 --> src/main.rs:2:15
  |
2 | fn is_copy<T: Copy>(_name: &str) {}
  |               ^^^^ required by this bound in `is_copy`

error[E0277]: the trait bound `Vec<i32>: Copy` is not satisfied
 --> src/main.rs:7:15
  |
7 |     is_copy::<Vec<i32>>("Vec<i32>");
  |               ^^^^^^^^ the trait `Copy` is not implemented for `Vec<i32>`
  |
note: required by a bound in `is_copy`
 --> src/main.rs:2:15
  |
2 | fn is_copy<T: Copy>(_name: &str) {}
  |               ^^^^ required by this bound in `is_copy`

error[E0277]: the trait bound `&mut i32: Copy` is not satisfied
 --> src/main.rs:8:15
  |
8 |     is_copy::<&mut i32>("&mut i32");
  |               ^^^^^^^^ the trait `Copy` is not implemented for `&mut i32`
  |
  = note: `Copy` is implemented for `&i32`, but not for `&mut i32`
note: required by a bound in `is_copy`
 --> src/main.rs:2:15
  |
2 | fn is_copy<T: Copy>(_name: &str) {}
  |               ^^^^ required by this bound in `is_copy`
help: consider removing the leading `&`-reference
  |
8 -     is_copy::<&mut i32>("&mut i32");
8 +     is_copy::<i32>("&mut i32");

Rust still has the same three ideas. The sigil or the type shows which, and the compiler refuses the surprising ones.

Going deeper

A copied header still points at the data

A Vec is three words: a pointer, a length and a capacity. clone builds a second buffer, and a slice &v[..] is a pointer plus a length into the first. std::ptr::eq is Rust’s identity check: on two references it compares addresses, where == compares what they point at.

Rustsrc/main.rsRun ↗
use std::mem::size_of;
use std::ptr;
fn main() {
let v = vec![1, 2, 3];
let copy = v.clone(); // a new Vec with its own buffer
let view = &v[..]; // a pointer and a length into v's buffer
println!("size_of Vec<i32>: {} bytes", size_of::<Vec<i32>>());
println!("size_of &[i32]: {} bytes", size_of::<&[i32]>());
println!(
"v and copy share a buffer: {}",
ptr::eq(v.as_ptr(), copy.as_ptr())
);
println!(
"v and view share a buffer: {}",
ptr::eq(v.as_ptr(), view.as_ptr())
);
let a = 5;
let b = a;
let r1 = &a;
let r2 = &a;
println!("a and b are one variable: {}", ptr::eq(&a, &b));
println!("r1 and r2 are one place: {}", ptr::eq(r1, r2));
}
$ cargo run
size_of Vec<i32>: 24 bytes
size_of &[i32]:   16 bytes
v and copy share a buffer: false
v and view share a buffer: true
a and b are one variable:  false
r1 and r2 are one place:   true
let copy = v.clone(); let view = &v[..];
STACKHEAPv: Vec<i32>pointerlen 3cap 3copy: Vec<i32>pointerlen 3cap 3view: &[i32]pointerlen 3v and view123copy's own buffer123
The three header fields are drawn in a convenient order. view shares v's buffer, copy does not. The compiler tracks view, so v cannot grow or be dropped while view is in use.

Shallow and deep

A shallow copy shares what is inside; your language’s block above showed one. In Rust a Vec or String field owns its buffer, so .clone() makes a new one for each. Sharing is something you ask for, with Rc (several owners; Ownership):

Rustsrc/main.rsRun ↗
use std::ptr;
use std::rc::Rc;
#[derive(Clone)]
struct Team {
name: String,
members: Vec<String>,
}
fn main() {
let a = Team {
name: String::from("red"),
members: vec![String::from("ana")],
};
let mut b = a.clone(); // copies the name, the Vec and every String in it
b.members[0].push('!');
println!("a: {} {:?}", a.name, a.members);
println!("b: {} {:?}", b.name, b.members);
println!(
"same member buffer: {}",
ptr::eq(a.members.as_ptr(), b.members.as_ptr())
);
// Sharing is something you ask for: Rc::clone copies the arrow, not the Team.
let shared = Rc::new(a);
let other = Rc::clone(&shared);
println!("same Team: {}", Rc::ptr_eq(&shared, &other));
println!("owners of the Team: {}", Rc::strong_count(&shared));
}
$ cargo run
a: red ["ana"]
b: red ["ana!"]
same member buffer: false
same Team: true
owners of the Team: 2

Rc::clone copied an arrow and added an owner, which is the “shared” case of the table at the top.

Practice

Predict, then run

Say what this prints before you open the answer. Each line depends on whether clone() or let first = scores[0] made a copy.

Rustsrc/main.rsRun ↗
fn main() {
let mut scores = vec![10, 20, 30];
let mut copy = scores.clone();
for s in &mut copy {
*s += 1;
}
let first = scores[0];
scores[0] = 99;
let total: i32 = scores.iter().sum();
println!("scores = {scores:?}");
println!("copy = {copy:?}");
println!("first = {first}");
println!("total = {total}");
}
Show the output
Rustsrc/main.rsRun ↗
fn main() {
let mut scores = vec![10, 20, 30];
let mut copy = scores.clone();
for s in &mut copy {
*s += 1;
}
let first = scores[0];
scores[0] = 99;
let total: i32 = scores.iter().sum();
println!("scores = {scores:?}");
println!("copy = {copy:?}");
println!("first = {first}");
println!("total = {total}");
}
$ cargo run
scores = [99, 20, 30]
copy   = [11, 21, 31]
first  = 10
total  = 149

clone() gave copy its own buffer. scores[0] is an i32, so first is a copy, taken before scores[0] = 99.

Change the caller's list

Add one to every age, in place. The parameter is already a &mut Vec<Person>, so loop with people.iter_mut(); &mut people would be a reference to a reference. The checks include an empty list.

Starterexercises/birthday.rs
struct Person {
name: String,
age: u32,
}
fn ages(people: &Vec<Person>) -> Vec<u32> {
let mut out = Vec::new();
for p in people {
out.push(p.age);
}
out
}
/// Adds one to every age, in the caller's list.
fn birthday(people: &mut Vec<Person>) {
todo!(
"people is already a reference: loop with `people.iter_mut()`, so each `p` is a `&mut Person`"
)
}
fn main() {
let mut people = vec![
Person {
name: String::from("Ada"),
age: 36,
},
Person {
name: String::from("Linus"),
age: 54,
},
Person {
name: String::from("Grace"),
age: 85,
},
];
let mut others = vec![Person {
name: String::from("Bo"),
age: 20,
}];
birthday(&mut people);
assert_eq!(ages(&people), [37, 55, 86]);
assert_eq!(people[0].name, "Ada");
// Only the list you pass is changed.
assert_eq!(ages(&others), [20]);
// An empty list is fine.
let mut nobody: Vec<Person> = Vec::new();
birthday(&mut nobody);
assert!(nobody.is_empty());
birthday(&mut others);
assert_eq!(ages(&others), [21]);
println!("all checks passed");
}

Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.

Show a solution
One solutionexercises/birthday.solution.rsRun ↗
struct Person {
name: String,
age: u32,
}
fn ages(people: &Vec<Person>) -> Vec<u32> {
let mut out = Vec::new();
for p in people {
out.push(p.age);
}
out
}
/// Adds one to every age, in the caller's list.
fn birthday(people: &mut Vec<Person>) {
for p in people.iter_mut() {
p.age += 1;
}
}
fn main() {
let mut people = vec![
Person {
name: String::from("Ada"),
age: 36,
},
Person {
name: String::from("Linus"),
age: 54,
},
Person {
name: String::from("Grace"),
age: 85,
},
];
let mut others = vec![Person {
name: String::from("Bo"),
age: 20,
}];
birthday(&mut people);
assert_eq!(ages(&people), [37, 55, 86]);
assert_eq!(people[0].name, "Ada");
// Only the list you pass is changed.
assert_eq!(ages(&others), [20]);
// An empty list is fine.
let mut nobody: Vec<Person> = Vec::new();
birthday(&mut nobody);
assert!(nobody.is_empty());
birthday(&mut others);
assert_eq!(ages(&others), [21]);
println!("all checks passed");
}
$ cargo run
all checks passed

Look without copying

Return the biggest age. Person does not derive Clone, so you cannot copy the list: borrow it with for p in people, where each p is a &Person. The checks include an empty list.

Starterexercises/oldest-age.rs
// `Person` does not derive `Clone`, so the list cannot be copied: you must borrow it.
struct Person {
name: String,
age: u32,
}
/// The biggest age in the list, or 0 for an empty list.
fn oldest_age(people: &Vec<Person>) -> u32 {
todo!("loop with `for p in people` (each `p` is a `&Person`) and keep the biggest `p.age`")
}
fn main() {
let people = vec![
Person {
name: String::from("Ada"),
age: 36,
},
Person {
name: String::from("Grace"),
age: 85,
},
Person {
name: String::from("Linus"),
age: 54,
},
];
assert_eq!(oldest_age(&people), 85);
assert_eq!(people[1].age, 85);
// The list is still ours afterwards, and Person has no Clone to copy it with.
assert_eq!(people.len(), 3);
assert_eq!(people[0].name, "Ada");
// An empty list has no oldest person; the answer is 0.
let nobody: Vec<Person> = Vec::new();
assert_eq!(oldest_age(&nobody), 0);
// One person.
let one = vec![Person {
name: String::from("Bo"),
age: 20,
}];
assert_eq!(oldest_age(&one), 20);
println!("all checks passed");
}

Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.

Show a solution
One solutionexercises/oldest-age.solution.rsRun ↗
// `Person` does not derive `Clone`, so the list cannot be copied: you must borrow it.
struct Person {
name: String,
age: u32,
}
/// The biggest age in the list, or 0 for an empty list.
fn oldest_age(people: &Vec<Person>) -> u32 {
let mut best = 0;
for p in people {
if p.age > best {
best = p.age;
}
}
best
}
fn main() {
let people = vec![
Person {
name: String::from("Ada"),
age: 36,
},
Person {
name: String::from("Grace"),
age: 85,
},
Person {
name: String::from("Linus"),
age: 54,
},
];
assert_eq!(oldest_age(&people), 85);
assert_eq!(people[1].age, 85);
// The list is still ours afterwards, and Person has no Clone to copy it with.
assert_eq!(people.len(), 3);
assert_eq!(people[0].name, "Ada");
// An empty list has no oldest person; the answer is 0.
let nobody: Vec<Person> = Vec::new();
assert_eq!(oldest_age(&nobody), 0);
// One person.
let one = vec![Person {
name: String::from("Bo"),
age: 20,
}];
assert_eq!(oldest_age(&one), 20);
println!("all checks passed");
}
$ cargo run
all checks passed

Read more