Ownership & Borrowing

Problems

let a = 10;
let b = a;
println!("{}", a); // 10
println!("{}", b); // 10
10
10
let a = String::from("Hello");
let b = a; // the value of a is moved to b, a is no longer valid
println!("{}", a); // error 
println!("{}", b); 
Error: borrow of moved value: `a`

Oops error! Why the first one is okay but the second one is not?

Memory Visualization

Two example above yield different results because int implements the Copy trait, while String does not.

  • Objects which implement Copy traits are copied when they are assigned to another variable.
  • Objects which do not implement Copy trait are moved when they are assigned to another variable.
let a = 10;
let b = a;

// print the address of a and b, they are different
println!("{:p}", &a);
println!("{:p}", &b); 
0x16b856810
0x16b856814

graph TD
    a -- owns --> aliteral["10"]
    b -- owns --> bliteral["10"]

While in the case of String, there is only one object in the memory (no implicit Copy), both a and b point to the same object.

graph TD
    a -- owns --> aliteral["Hello"]
    b -- owns --> aliteral
    classDef errorLabel fill:#f00,stroke:none,color:#fff;
    errLabel[/"Compile Error: Multiple owners of the same node"/]
    class errLabel errorLabel

Rust does NOT allow multiple owners of the same object. This is why the second example fails. (we will discuss this in more detail later)

To achieve the same result as the first example, we can use the clone method to create a new object.

let a = String::from("Hello");
let b = a.clone();

println!("{}", a); // Hello
println!("{}", b); // Hello

// print the address of a and b
println!("{:p}", &a);
println!("{:p}", &b);
Hello
Hello
0x16b8567e0
0x16b8567f8

graph TD
    a -- owns --> aliteral["Hello"]
    b -- owns --> bclone["Hello"]

Copy vs Clone

Copy is a special trait that is used for types that can be copied by simply copying bits. This is mainly used for simple types like integers, floats, and booleans. If a type implements the Copy trait, an older variable is still usable after assignment.

While Clone trait is used for types that cannot be copied by simply copying bits. If a type implements the Clone trait, we can create a new object by cloning the original object. This often involves allocating memory on the heap and deep copying the original object.

From now on, our focus will be on non-Copy types.

Stack vs Heap

Stack and heap are two different memory regions in a program.

Stack

  • is used for static memory allocation
  • is used to store local variables and function call information
  • the size must be known at compile time
  • is faster than heap memory
  • when a function is called, a block of memory is allocated on the stack for the function to use
  • when a function call ends, the block of memory is deallocated

Heap

  • is used for dynamic memory allocation
  • is used to store data whose size is not known at compile time
  • is slower than stack memory because it is allocated at runtime
  • when a block of memory is allocated on the heap, a pointer to that memory is returned
  • to access the data in the heap, we need to follow the pointer

https://endjin.com/blog/2022/07/understanding-the-stack-and-heap-in-csharp-dotnet

Source: https://endjin.com/blog/2022/07/understanding-the-stack-and-heap-in-csharp-dotnet

Ownership Rules

  • Each value in Rust has a variable that is its owner.
  • There can only be one owner at a time.
  • When the owner goes out of scope, the value will be dropped.

Let’s see them in action

In Local Scope

let x = String::from("Hello");
let y = x; // the owner is transferred to y
// x no longer valid

println!("{}", x); 
Error: borrow of moved value: `x`

graph TD
    x
    y -- owns --> bliteral["Hello"]
    classDef invalid fill:#f00;
    class x invalid

In Parameter

fn uppercase(s: String) -> String {
    s.to_uppercase()
}

let x = String::from("Hello");
println!("{}", uppercase(x)); // the value of x is moved to the function parameter

println!("{}", x); // error
Error: borrow of moved value: `x`

graph TD
    x
    s -- owns --> bliteral["Hello"]
    classDef invalid fill:#f00;
    class x invalid

Fix: Clone

Wow, so many errors! Let’s fix them one by one.

The easiest way to fix the errors is to clone the String object. This way, we can create a new object on the heap and assign it to the new variable.

let x = String::from("Hello");
let y = x.clone();

println!("{}", x); // Hello
println!("{}", y); // Hello
Hello
Hello

graph TD
    x -- owns --> aliteral["Hello"]
    y -- owns --> bliteral["Hello"]

fn uppercase(s: String) -> String {
    s.to_uppercase()
}

let x = String::from("Hello");
println!("{}", uppercase(x.clone())); // clone the value of x

println!("{}", x); // Hello
HELLO
Hello

graph TD
    x -- owns --> aliteral["Hello"]
    s -- owns --> bliteral["Hello"]

Solved!

But wait, we are using more memory than necessary.

Better Fix: Borrowing

We can do better by using borrowing. Borrowing allows us to pass a reference to the object instead of passing the object itself. This way, we can avoid creating a new object on the heap.

fn test() {
    let x = String::from("Hello");
    let y = &x;

    println!("{}", x); // Hello
    println!("{}", *y); // Hello
}

test();
Hello
Hello

graph TD
    x -- owns --> aliteral["Hello"]
    y -. borrows .-> x

fn uppercase(s: &String) -> String {
    s.to_uppercase()
}

let x = String::from("Hello");
println!("{}", uppercase(&x));

println!("{}", x); // Hello
HELLO
Hello

graph TD
    x -- owns --> aliteral["Hello"]
    s -. borrows .-> x

Fix: Parameter Forwarding

fn uppercase(s: String) -> (String, String) {
    let result = s.to_uppercase();
    (result, s) // return `s` as well (return back the ownership)
}

let x = String::from("Hello");
let (upper, x) = uppercase(x);

println!("{}", upper); // HELLO
println!("{}", x); // Hello
HELLO
Hello

Inside the function call, s owns the object.

graph TD
    s -- owns --> aliteral["Hello"]
    x
    classDef invalid fill:#f00;
    class x invalid

After the function call, the ownership is transferred to x

graph TD
    x -- owns --> aliteral["Hello"]
    upper -- owns --> bliteral["HELLO"]

Mutable Reference

By default, references are immutable. This means we cannot change the value of the object through the reference.

fn change_to_upper(s: &String) {
    s.make_ascii_uppercase() // trying to mutate
}

let x = String::from("Hello");
change_to_upper(&x);

println!("{}", x); // error
Error: cannot borrow `*s` as mutable, as it is behind a `&` reference

To be able to change the value of the object, we need to use a mutable reference. &mut

// &mut is a mutable reference
fn change_to_upper(s: &mut String) {
    s.make_ascii_uppercase()
}

let mut x = String::from("Hello");
change_to_upper(&mut x);

println!("{}", x); // HELLO
HELLO

Exclusive Access

There are additional rules for references.

1. Only one mutable reference to an object is allowed at a time.

//these are all allowed
fn test_multiple_reference() {
    let x = String::from("Hello");
    let y = &x;
    let z = &x;

    println!("{}", x); // Hello
    println!("{}", y); // Hello
    println!("{}", z); // Hello
}

test_multiple_reference();
Hello
Hello
Hello

graph TD
    x -- owns --> aliteral["Hello"]
    y -. borrows .-> x
    z -. borrows .-> x

fn test_multiple_write_reference() {
    let mut x = String::from("Hello");
    let y = &mut x;
    let z = &mut x;

    println!("{}", x); // hello
    println!("{}", y); // hello
    println!("{}", z); // hello
}

test_multiple_write_reference();
Error: cannot borrow `x` as mutable more than once at a time
Error: cannot borrow `x` as immutable because it is also borrowed as mutable

graph TD
    x -- owns --> aliteral["Hello"]
    y -. mut borrows .-> x
    z -. mut borrows .-> x
    classDef invalid fill:#f00;
    class z invalid

Why?

To prevent race conditions. If we have multiple mutable references to an object and they run concurrently, they can change the object in an unpredictable way.

2. When an object has a mutable reference, it cannot be accessed by other references (both mutable & immutable).

fn test_one_write_reference_and_many_read_reference() {
    let mut x = String::from("Hello");
    let y = &mut x;
    let z = &x; // error

    println!("{}", y);
    println!("{}", z); 
}

test_one_write_reference_and_many_read_reference();
Error: cannot borrow `x` as immutable because it is also borrowed as mutable
Error: cannot borrow `x` as immutable because it is also borrowed as mutable

graph TD
    x -- owns --> aliteral["Hello"]
    y -. mut borrows .-> x
    z -. borrows .-> x
    classDef invalid fill:#f00;
    class z invalid

Struct

Struct Method

struct Person {
    name: String,
    age: u8,
}

impl Person {
    fn greet(self) {
        println!("Hello, my name is {}", self.name);
    }
}

let person = Person {
    name: String::from("Alice"),
    age: 30,
};
person.greet();
person.greet(); //error
Error: use of moved value: `person`

graph TD
    Person:greet -- owns --> aliteral["person{Alice, 30}"]
    person
    classDef invalid fill:#f00;
    class person invalid

To understand it better, actually

impl Person {
    fn greet(self) {} 
}

is equivalent to

fn greet(self: Person) {}
fn greet(person: Person) {
    println!("Hello, my name is {}", person.name);
}

let person = Person {
    name: String::from("Alice"),
    age: 30,
};
greet(person);
greet(person); //error
Error: use of moved value: `person`

We have learned that in the case of function call, the function can take ownership of the object. This is why the above code does not work.

Fix

The fix is actually similar to the one we have seen before. We can use borrowing to pass a reference, i.e. &self instead of self.

struct Person {
    name: String,
    age: u8,
}

impl Person {
    fn greet(&self) {
        println!("Hello, my name is {}", self.name);
    }
}

let person = Person {
    name: String::from("Alice"),
    age: 30,
};
person.greet();
person.greet();
Hello, my name is Alice
Hello, my name is Alice

The code above is equivalent to

fn greet(person: &Person) {
    println!("Hello, my name is {}", person.name);
}

greet(&person);
greet(&person);
Hello, my name is Alice
Hello, my name is Alice

Slices

The power of borrowing looks even more impressive when we talk about slices.

struct Person {
    name: String,
    age: u8,
}

fn first(v: &Vec<Person>) -> &Person {
    &v[0]
}

fn test_slices() {
    let mut people = vec![
        Person {
            name: String::from("Alice"),
            age: 30,
        },
        Person {
            name: String::from("Bob"),
            age: 25,
        }
    ];

    let first_person = first(&people);
    println!("{}", first_person.name);

    people.remove(0);
}

test_slices();
Alice

Ok, it runs well. But do you notice the problem?

After remove(0), the first_person doesn’t exist anymore. But we still have a reference to it. This is a dangling reference, which is a common problem in programming.

Before people.remove(0), this is what we have:

graph LR
    subgraph People
        0["0: Alice"] --- 1["1: Bob"]
    end
    first_person --> 0

But after people.remove(0), the object is removed from the memory. first_person is now a dangling reference.

graph LR
    subgraph People
        1["0: Bob"]
    end
    first_person --> 0["?"]

Before seeing how Rust handles this problem, let’s see how Golang handles it.

Rewritten in Golang:

package main

import (
    "fmt"
    "slices"
)

type Person struct {
    name string
    age  uint8
}

func first(v *[]Person) *Person {
    return &(*v)[0]
}

func testSlices() {
    people := []Person{
        {
            name: "Alice",
            age:  30,
        },
        {
            name: "Bob",
            age:  25,
        },
    }

    firstPerson := first(&people)
    fmt.Println(firstPerson.name)

    // remove the first element
    _ = slices.Delete(people, 0, 1)

    fmt.Println(firstPerson.name)
}

func main() {
    testSlices()
}
> go run main.go
Alice
Bob

What is the second output? Bob! Isn’t it expected to point to Alice?

Now, let’s see how Rust handles this problem.

struct Person {
    name: String,
    age: u8,
}

fn first(v: &Vec<Person>) -> &Person {
    &v[0]
}

fn test_slices() {
    let mut people = vec![
        Person {
            name: String::from("Alice"),
            age: 30,
        },
        Person {
            name: String::from("Bob"),
            age: 25,
        }
    ];

    let first_person = first(&people);
    println!("{}", first_person.name);

    people.remove(0);
    
    println!("{}", first_person.name);
}

test_slices();
Error: cannot borrow `people` as mutable because it is also borrowed as immutable

Rust compiler is smart enough to prevent dangling references. It will give us a compile-time error if we try to access a dangling reference.

But what if we want to access the first element after removing it? We can, just explicitly clone the object.

// Important: need to make the struct Person cloneable
#[derive(Clone)]
struct Person {
    name: String,
    age: u8,
}

fn first(v: &Vec<Person>) -> &Person {
    &v[0]
}

fn test_slices() {
    let mut people = vec![
        Person {
            name: String::from("Alice"),
            age: 30,
        },
        Person {
            name: String::from("Bob"),
            age: 25,
        }
    ];

    let first_person = first(&people).clone(); // clone the value
    println!("{}", first_person.name);

    people.remove(0);
    
    println!("{}", first_person.name); // this reference is still valid
}

test_slices();
Alice
Alice

Before people.remove(0):

graph LR
    subgraph People
        0["0: Alice"] --- 1["1: Bob"]
    end

    people -- owns --> People
    first_person -- owns --> clone["Alice"]

After:

graph LR
    subgraph People
        0["0: Bob"]
    end

    people -- owns --> People
    first_person -- owns --> clone["Alice"]

Taking Ownership

How about if we don’t want to get the reference to the first element, but we want to take ownership of it? i.e. we don’t want &Person but Person.

Let’s try

struct Person {
    name: String,
    age: u8,
}

fn first(v: &Vec<Person>) -> Person {
    // return the array element, not the reference
    v[0]
}

fn test_slices() {
    let mut people = vec![
        Person {
            name: String::from("Alice"),
            age: 30,
        },
    ];

    let first_person = first(&people);
    println!("{}", first_person.name);
}

test_slices();
Error: cannot move out of index of `Vec<Person>`

Remember the ownership rules? When we return the object, the ownership is transferred to the caller.

Move can’t happen because the field is inside a Vec. And only one owner is allowed at a time.

graph LR
    subgraph People
        0["0: Alice"]
    end

    people -- owns --> People
    first_person -- owns --> 0

    classDef invalid fill:#f00;
    class first_person invalid

Hmm, but is it a special behavior of a Vec? Let’s try replacing Vec with our own “array”, People:

struct Person {
    name: String,
    age: u8,
}

struct People {
    person1: Person,
    person2: Person
}

fn first(v: &People) -> Person {
    v.person1
}

fn test_slices() {
    let people = People {
        person1: Person {
            name: String::from("Alice"),
            age: 30,
        },
        person2: Person {
            name: String::from("Bob"),
            age: 25,
        },
    };

    let first_person = first(&people);
    println!("{}", first_person.name);
}

test_slices();
Error: cannot move out of `v.person1` which is behind a shared reference

Wow, that also can’t be moved. Nice!

struct Person {
    name: String,
    age: u8,
}

fn first(p1: &Person, _p2: &Person) -> Person {
    *p1
}

fn test_slices() {
    let person1 = Person {
        name: String::from("Alice"),
        age: 30,
    };
    let person2 = Person {
        name: String::from("Bob"),
        age: 25,
    };

    let first_person = first(&person1, &person2);
    println!("{}", first_person.name);
}

test_slices();
Error: cannot move out of `*p1` which is behind a shared reference

It turns out we can’t move the object because it is owned by someone else. We can’t move the object out of the Vec because the Vec owns the object, we can’t move the object out of the People because the People owns the object.

Similar to borrowing rule in a real life. We can’t move an object from one owner to another without the owner’s consent.

So, let’s now get that consent!

struct People {
    person1: Person,
    person2: Person
}

// pass the People object, taking ownership
fn first(v: People) -> (People, Person) {
    // return back the ownership + the value
    (v, v.person1)
}

fn test_slices() {
    let people = People {
        person1: Person {
            name: String::from("Alice"),
            age: 30,
        },
        person2: Person {
            name: String::from("Bob"),
            age: 25,
        },
    };

    let (_people, first_person) = first(people);
    println!("{}", first_person.name);
}

test_slices();
Error: use of moved value: `v.person1`

Hmm…

std::mem::replace

std::mem::replace is a function that takes ownership of the object and replaces it with a new object. It’s worth noting that std::mem::replace is a safe function because it guarantees that the object will be replaced and not left dangling.

struct People {
    person1: Person,
    person2: Person
}

fn first(mut v: People) -> (People, Person) {
    // replace the value of person1
    let old_person1 = std::mem::replace(&mut v.person1, Person {
        name: String::from("Charlie"),
        age: 20,
    });
    (v, old_person1)
}

fn test_slices() {
    // need to make it mutable
    let mut people = People {
        person1: Person {
            name: String::from("Alice"),
            age: 30,
        },
        person2: Person {
            name: String::from("Bob"),
            age: 25,
        },
    };

    let (people, first_person) = first(people);
    println!("{}", first_person.name);

    println!("{}", people.person1.name);
}

test_slices();
Alice
Charlie

std::mem::take

std::mem::take is quite similar to std::mem::replace. The difference is that std::mem::take replaces the object with the default value of the object.

// need to derive Default
#[derive(Default)]
struct Person {
    name: String,
    age: u8,
}

// also need to derive Default
#[derive(Default)]
struct People {
    person1: Person,
    person2: Person
}

fn first(mut v: People) -> (People, Person) {
    // take the value of person1 and replace it with the default value
    let old_person1 = std::mem::take(&mut v.person1);
    (v, old_person1)
}

fn test_slices() {
    // need to make it mutable
    let mut people = People {
        person1: Person {
            name: String::from("Alice"),
            age: 30,
        },
        person2: Person {
            name: String::from("Bob"),
            age: 25,
        },
    };

    let (people, first_person) = first(people);
    println!("{}", first_person.name);

    println!("{}", people.person1.name); //empty
}

test_slices();
Alice

Applying to Vec

Let’s see how we can use std::mem::replace to take ownership of the first element of a Vec.

fn test_vec() {
    let mut v = vec![1, 2, 3];
    let value = std::mem::replace(&mut v[1], 0);

    println!("{:?}", v); // [1, 0, 3]
    println!("{}", value); // 2
}

test_vec();
[1, 0, 3]
2
fn test_vec() {
    let mut v = vec![1, 2, 3];
    let value = std::mem::take(&mut v[1]);

    println!("{:?}", v); // [1, 0, 3]
    println!("{}", value); // 2
}

test_vec();
[1, 0, 3]
2

Loop

Ownership rules may also confuse us when we use loops.

fn test_loop() {
    let nums = vec![1, 2, 3, 4, 5];

    for num in nums {
        println!("{}", num);
    }

    // let's print again
    for num in nums {
        println!("{}", num);
    }
}

test_loop();
Error: use of moved value: `nums`

Nah, tantrum again! What’s wrong?

When we call for num in nums, it implicitly calls nums.into_iter(). This method consumes the object and returns an iterator that takes ownership of the object.

Let’s take a look at .into_iter() signature:

fn into_iter(self) -> Self::IntoIter;

Since self is passed by value, it consumes the object. This is why we can’t use nums after the loop.

The alternative is to use iter() which borrows the object.

fn iter(&self) -> Iter<'_, T>;

Let’s try .iter()

fn test_loop() {
    let nums = vec![1, 2, 3, 4, 5];

    // .iter()
    for num in nums.iter() {
        println!("{}", num);
    }

    for num in nums.iter() {
        println!("{}", num);
    }
}

test_loop();
1
2
3
4
5
1
2
3
4
5

Or we can just loop over &nums:

fn test_loop() {
    let nums = vec![1, 2, 3, 4, 5];

    // &nums
    for num in &nums {
        println!("{}", num);
    }

    for num in &nums {
        println!("{}", num);
    }
}

test_loop();
1
2
3
4
5
1
2
3
4
5

Modification

.iter() returns an immutable reference to the object. This means we can’t modify the object through the iterator.

fn test_loop() {
    let mut nums = vec![1, 2, 3, 4, 5];

    for num in &nums {
        *num += 1
    }
    
    println!("{:?}", nums);
}

test_loop();
Error: cannot assign to `*num`, which is behind a `&` reference

We can’t modify because it is borrowed immutably. Let’s try borrowing mutably.

fn test_loop() {
    let mut nums = vec![1, 2, 3, 4, 5];

    for num in &mut nums {
        *num += 1
    }

    println!("{:?}", nums);
}

test_loop();
[2, 3, 4, 5, 6]

Or explicitly .iter_mut()

fn iter_mut(&mut self) -> IterMut<'_, T>;
fn test_loop() {
    let mut nums = vec![1, 2, 3, 4, 5];

    for num in nums.iter_mut() {
        *num += 1
    }

    println!("{:?}", nums);
}

test_loop();
[2, 3, 4, 5, 6]