One of the most important concepts to master in Rust is ownership and borrowing. Tons and tons of articles are solely dedicated to this narrow subject. This one tries to explain the concept with examples. I hope it helps you.
Ownership is a set of rules that govern how a Rust program manages memory. All programs have to manage the way they use a computer’s memory while running. Some languages have garbage collection that regularly looks for no-longer-used memory as the program runs; in other languages, the programmer must explicitly allocate and free the memory. Rust uses a third approach: memory is managed through a system of ownership with a set of rules that the compiler checks. If any of the rules are violated, the program won’t compile. None of the features of ownership will slow down your program while it’s running.
First taste of ownership
Let’s start with a simple Java snippet:
public void own(String text) {}
public static void main(String[] args) {
var text = "my text";
own(text);
System.out.println(text);
}
Even with a passing knowledge of Java, the code is straightforward.
We can "translate" the above snippet to Rust:
fn own(_: String) {}
fn main() {
let text: String = String::from("my text"); (1)
own(text);
println!("{}", text);
}
| 1 | The String::from() function takes a type reference to create a String |
Rust code looks pretty similar. Yet, the compiler complains:
error[E0382]: borrow of moved value: `text`
--> src/main.rs:11:20
|
9 | let text: String = String::from("my text");
| ---- move occurs because `text` has type `String`, which does not implement the `Copy` trait
10 | own(text);
| ---- value moved here
11 | println!("{}", text);
| ^^^^ value borrowed here after move
|
note: consider changing this parameter type in function `own` to borrow instead if owning the value isn't necessary
--> src/main.rs:6:14
|
6 | fn own(text: String) {}
| --- ^^^^^^ this parameter takes ownership of the value
| |
| in this function
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
|
10 | own(text.clone());
| ++++++++
The Rust compiler is very helpful compared to the compilers of other stacks I have used in the past.
It explains the problem and pinpoints its exact location:
we pass the text variable and the own() function takes ownership of the value.
It’s illegal to use it in the println!() macro afterwards.
Clone and Copy
We can follow the compiler’s advice and call clone(), because String implements the Clone trait.
clone() creates a deep copy of the structure.
The code compiles and runs successfully with one caveat:
there are now two String structures.
It takes twice as much space in memory.
Note that if the structure implements Copy as well, you don’t need to call clone() explicitly.
Let’s create our own type to showcase this:
#[derive(Debug, Copy, Clone)]
struct Dummy {}
fn own(_: Dummy) {}
fn main() {
let dummy = Dummy {};
own(dummy);
println!("{:?}", dummy);
}
Note that making a structure implement Copy will implicitly clone() it every time it needs to move.
You can’t undo it on a per-call basis.
It can be memory-hungry if you do it for large structures that you pass around frequently.
You have been warned.
Passing by reference
So far, we passed structures by value.
When we do so, the called function takes ownership of the structure.
We can let the function borrow the structure instead, and pass it by reference.
As in other languages, one can reference a structure with the & operator.
Let’s rewrite the code with reference passing.
#[derive(Debug)]
struct Dummy {}
fn borrow(_: &Dummy) {}
fn main() {
let dummy = Dummy { };
borrow(&dummy);
println!("{:?}", dummy);
}
The code compiles and runs. Borrowing allows reading the structure state.
#[derive(Debug)]
struct Dummy {
foo: String,
}
fn borrow(dummy: &Dummy) {
println!("{:?}", dummy.foo);
}
fn main() {
let dummy = Dummy { foo: String::from("Foo") };
borrow(&dummy);
println!("{:?}", dummy); (1)
}
| 1 | Prints Dummy { foo: "Foo" } |
Mutating data
In the previous section, I described how to read data. What happens if we want to write data?
#[derive(Debug)]
struct Dummy {
foo: String,
}
fn main() {
let dummy = Dummy { foo: String::from("Foo") };
dummy.foo = String::from("Bar");
}
We get the following error:
error[E0594]: cannot assign to `dummy.foo`, as `dummy` is not declared as mutable
--> src/main.rs:8:5
|
8 | dummy.foo = String::from("Bar");
| ^^^^^^^^^ cannot assign
|
help: consider changing this to be mutable
|
7 | let mut dummy = Dummy { foo: String::from("Foo") };
| +++
In Rust parlance, writing data is known as mutating.
To mark the variable as being mutable, we use the mut keyword.
#[derive(Debug)]
struct Dummy {
foo: String,
}
fn main() {
let mut dummy = Dummy { foo: String::from("Foo") };
dummy.foo = String::from("Bar");
}
You may want a function to change the state of a parameter.
For that, you need to mark the parameter as mut:
#[derive(Debug)]
struct Dummy {
foo: String,
}
fn mutate(mut dummy: Dummy) { (1)
dummy.foo = String::from("Bar");
println!("{:?}", dummy.foo);
}
fn main() {
let dummy = Dummy { foo: String::from("Foo") };
mutate(dummy);
}
| 1 | mut fixes the issue |
Finally, we can both borrow and mutate.
For that, we need to mark both the variable and the parameter as mut.
#[derive(Debug)]
struct Dummy {
foo: String,
}
fn mutate(dummy: &mut Dummy) { (1)
dummy.foo = String::from("Bar");
println!("Inside: {:?}", dummy.foo);
}
fn main() {
let mut dummy = Dummy { foo: String::from("Foo") }; (2)
mutate(&mut dummy); (3)
println!("Outside: {:?}", dummy.foo);
}
| 1 | Mark the parameter as mut |
| 2 | Mark the variable as mut |
| 3 | Pass a mutable reference |
Summary
| Behavior | How? |
|---|---|
Take ownership |
Pass by value |
Duplicate the value |
Implement |
Automatically duplicate the value |
Implement |
Borrow |
Pass by reference |
Mutate the value |
Use |
Mutate behind a reference |
Use |