Rust Ownership — Animated Intro
The web is full of great content explaining how and why to use the move & borrow semantics in Rust. But it is still hard to build an intuition for it.
Although it’s important to struggle with abstract concepts to build the intuition, I would still like to ease that struggle a tiny bit for you. — At least for the visual learners among you.
Copy
A copy can only happen with types that implement the Copy trait. If you don’t know what a trait is, read this.
When a copyable value gets assigned to a new variable, it is simply copied.
#[derive(Copy)] // ipmlement the Copy trait
struct Circle; // define a type called Cirlcle
let purple = Circle; // create a Circle instance and assign it to a variable
let green = purple; // assign the value of `purple` to the variable `green`
Are you kidding!? … It’s ludicrous! … Do I really need to add Copy to all my types? … What about HUGE String instances? … I certainly don’t want to copy those around … Isn’t Rust supposed to be fast and memory efficient!?
Move
Each value must be “owned” by exactly one variable.
If the type value cannot be copied, the value gets moved instead. As a side effect, the initial variable will lose access to the value and become useless.
struct Circle; // define a type called Cirlcle, that does NOT implement Copy
let purple = Circle; // create a Circle instance and assign it to a variable.
let green = purple; // assign the value of `purple` to the variable `green`
// from a point on, the `purple` variable can
// no longer be used.
Accessing the purple variable after moving its value to green would cause a compile error, as shown in the rust book.
Are you saying that … if a value does not implement Copy … and I pass it to a function as an argument … the function will eat my value … and it is lost forever? … hmm … maybe I could rescue that poor value by using it as return value …. but what about the actual return value?! … that is so silly … Rust just makes no sense!
Borrow
Often, you would rather not copy or move a value, but still pass it along.
As a function argument, for example. After the function is done with your value, you want to continue using your variable and its value outside the function.
This is possible by creating a shared reference to the value, and passing that reference instead of the actual value along.
This is called borrowing in Rust.
You can create multiple shared references, also known as borrows, to the same value. That’s why they are “shared”.
There is a major disadvantage, though.
As long as a shared reference to a value exists, it cannot be mutated or moved.
As soon as all Borrows are gone, the initial owning variable gets the full control back.
struct Circle;
let purple = Circle;
fn fancy_function(green: &Circle){
// do something with borrowed value until the function ends.
}
// the value of purple get's borrowed with & and assigned to the parameter
// of the fancy_function named "green".
fancy_function(&purple);
Alright … alright! But what if I want to mutate my value in the function. Let’s say … I wish to change the diameter of the circle! … What should I do?
Mutable Borrow
In other cases, you want write-access to a value without copying or moving it. The shared borrow with ampersand (&) won’t help you here, since it will make the value read-only as long a borrow exists.
Arghh … This whole topic suddenly becomes existential! … What does it mean to exist? … Is living and existing the same? … Does the start and end of existence define someone's lifetime? … Lifetimes … damn … that’s another perplexing Rust concept I need to learn soon… enough! … I need to concentrate! … Back to mutable borrows!
For these cases, Rust offers you mutable borrows (&mut). Unlike with shared borrows, you are not allowed to have any additional borrow to that value. Even the originally owning variable is not allowed to read or mutate the value, as long as the mutable borrow exists.
struct Circle{diameter: u32};
let purple = Circle{diameter: 5};
fn fancy_function(green: &mut Circle){
green.diameter = 10;
// at the end of this function, "green" ceases to exist
}
// the value of purple get's borrowed with mut& and assigned to the parameter
// of the fancy_function named "green".
fancy_function(&mut purple);
but … but … what if … I really … really … really! … want multiple shared references to a value that allow mutability! … This crazy language is soooo … restrictive!
Smart Pointers
For most cases, copy, move, shared and mutable references are everything you need. They have been carefully designed and restricted so that a large amount of common programming mistakes are more or less impossible.
For cases where you require additional flexibility and capabilities for your references, Rust got some remarkable smart pointer types for you. They are safe to use, too.
Ok … I guess … I will go now, … and skim through the smart pointer documentation on my own. … Let’s see what other pointer types are out there, … just in case I need them in the future.
See u later alligator! … and sorry for the bad attitude.
Outro
Share the Animations
If you think that the animations in this article are useful, just copy and share them. I release them under the Creative Commons BY-SA 4.0 licence.
Feedback and contact
Please let me know if you would like me to write more articles like this about Rust. Positive or constructive feedback in the comments section is always welcome, and please leave a few claps if you liked it.
You can also find me on LinkedIn.