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:

let s1 = String::from("hello");
let s2 = s1;
 
println!("{}, world!", s1);
$ 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 the Copy 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 the Drop trait. If the type needs something special to happen when the value goes out of scope and we add the Copy 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.

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);
    println!("The length of '{}' is {}.", s1, len);
}
 
fn calculate_length(s: &String) -> usize {
    s.len()
}

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:

fn main() {
    let mut s = String::from("hello");
    change(&mut s);
}
 
fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

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:

let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;

We also cannot have a mutable reference while we have an immutable one to the same value.

let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM

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.