Ownership is Rustโs most unique feature and has deep implications for the rest of the language. It enables Rust to make memory safety guarantees without needing a garbage collector.
Ownership is a set of rules that govern how a Rust program manages memory.
Main purpose of ownership is to manage heap data (Stack and Heap).
Rules:
- Each value in Rust has an owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
The memory is automatically returned once the variable that owns it goes out of scope.
When a variable goes out of scope, Rust calls a special drop
function.
Rust calls drop
automatically at the closing curly bracket.
Move
Assigning a value to another variable moves it.
When we assign the value of a variable that holds a pointer to the value on the heap, Rust considers this variable no longer valid. This will raise a compile error:
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:5:28
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s1;
| -- value moved here
4 |
5 | println!("{}, world!", s1);
| ^^ value borrowed here after move
|
= 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
|
3 | let s2 = s1.clone();
| ++++++++
For more information about this error, try `rustc --explain E0382`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error
We would say that s1
was moved into s2
.
That solves our problem! With only s2 valid, when it goes out of scope it alone will free the memory, and weโre done.
In addition, thereโs a design choice thatโs implied by this: Rust will never automatically create โdeepโ copies of your data. Therefore, any automatic copying can be assumed to be inexpensive in terms of runtime performance.
Copy
For types that are stored on the stack, the Copy
trait can be implemented.
Rust has a special annotation called the
Copy
trait that we can place on types that are stored on the stack, as integers are (weโll talk more about traits in Chapter 10). If a type implements theCopy
trait, variables that use it do not move, but rather are trivially copied, making them still valid after assignment to another variable.
Rust wonโt let us annotate a type with
Copy
if the type, or any of its parts, has implemented theDrop
trait. If the type needs something special to happen when the value goes out of scope and we add theCopy
annotation to that type, weโll get a compile-time error.
Functions
Passing a variable to a function will move or copy, just as assignment does.
Returning values can also transfer ownership.
Reference and Borrowing
&
is used to represent reference. It allows the variable/function to refer to some value without taking ownership of it.
The opposite of referencing by using &
is dereferencing, which is accomplished with the dereference operator, *
.
We call the action of creating a reference borrowing.
Mutable references
Just as variables are immutable by default, so are references. Weโre not allowed to modify something we have a reference to. But we can create a mutable reference:
Mutable references have one big restriction: if you have a mutable reference to a value, you can have no other references to that value. The benefit of having this restriction is that Rust can prevent data races at compile time. For example, this code will fail:
We also cannot have a mutable reference while we have an immutable one to the same value.
Users of an immutable reference donโt expect the value to suddenly change out from under them. However, multiple immutable references are allowed because no one who is just reading the data has the ability to affect anyone elseโs reading of the data.
Dangling references
In Rust the compiler guarantees that references will never be dangling references: if you have a reference to some data, the compiler will ensure that the data will not go out of scope before the reference to the data does.
Recap
- At any given time, you can have either one mutable reference or any number of immutable references.
- References must always be valid.