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?
Two example above yield different results because int
implements the Copy
trait, while String
does not.
Copy
traits are copied when they are assigned to another variable.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
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.
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
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 and heap are two different memory regions in a program.
Source: https://endjin.com/blog/2022/07/understanding-the-stack-and-heap-in-csharp-dotnet
Let’s see them in action
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`
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`
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
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
Solved!
But wait, we are using more memory than necessary.
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
fn uppercase(s: &String) -> String {
s.to_uppercase()
}
let x = String::from("Hello");
println!("{}", uppercase(&x));
println!("{}", x); // Hello
HELLO
Hello
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.
After the function call, the ownership is transferred to x
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
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
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
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
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`
To understand it better, actually
is equivalent to
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.
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
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:
But after people.remove(0)
, the object is removed from the memory. first_person
is now a dangling reference.
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()
}
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)
:
After:
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.
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
Let’s see how we can use std::mem::replace
to take ownership of the first element of a Vec
.
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:
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.
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
.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()