Understanding Rust Ownership Tutorial

In this Rust tutorial we learn how ownership works in Rust. We learn the rules of ownership, how to transfer ownership and what happens when an owner goes out of scope.

Here's a table of contents of what you'll learn in this lesson:
(click on a link to skip to its section)

Let's jump right in.

What-is-ownership

The technical details of ownership are somewhat complex, but the concept is fairly simple. Simply put, ownership is the owner of a value.

First, let’s take a quick look at the ownership rules in Rust.

  • A variable is the owner of a value.
  • Only a single owner is allowed.
  • When the owner goes out of scope, the value is lost.
Scope

Scope is the range in which an item is valid. As an example, let’s consider a variable that’s declared inside a function.

Example:
fn log() {

    // msg is not valid at this point
    // it doesn't exist yet

    let msg = "Hello World";
    // now that msg is declared it's
    // valid from this point forward.
    // we can use it, it has scope

}
// the curly braces ends the function.
// msg will not be available beyond
// this point, so it goes out of scope

Before the msg variable has been defined, we can’t use it because it doesn’t exist. When msg is defined it gets scope, it can be used from that point forward until it loses scope.

When the function ends, msg will not be available anymore so it goes out of scope. As mentioned in the rules of ownership, once a variable (the owner of the value) goes out of scope, the value is lost.

We can demonstrate this by trying to access msg after the function call.

Example:
fn main() {

    log();

    println!("{}", msg);

}

fn log() {
    let msg = "Hello World";
}

Rust won’t allow it because the msg variable lost scope at the end of the log function.

If we run the application, the compiler will raise the following error.

Output:
5 |println!("{}", msg);
  |               ^^^ not found in this scope
Transfer Ownership

Rust only allows a value to have a single owner. As an example, let’s consider what happens when we assign the value of one variable, to another.

Example:
fn main() {

    let a = 10;

    let b = a;

    println!("A: {}", a);
    println!("B: {}", b);

}

When we assign a to b, it creates a copy of the value 10 and assigns it to a. They don’t share the value 10, each value occupies its own space in memory.

When we try the same thing with the String type, the value is moved, not copied.

Example:
fn main() {

    let a = String::from("Hello");
    let b = a;

    println!("A: {}", a);
    println!("B: {}", b);

}

This time, when we assign a to b, the value is moved to b, not copied. Because a value can only have one owner, the compiler will raise an error when we try to access a.

Output:
3 |     let a = String::from("Hello");
  |         - move occurs because `a` has type `std::string::String`, which does not implement the `Copy` trait
4 |     let b = a;
  |             - value moved here
5 |
6 |     println!("A: {}", a);
  |                       ^ value borrowed here after move

The error above explains that the String type can’t be copied, so the value is moved to b. So, ownership is transfered from a to b.

Summary: Points to remember

  • Rust’s memory management uses ownership and borrowing to replace runtime garbage collection and make memory safety guarantees at compile time.
  • There are three rules of ownership:
    • A variable owns a value.
    • At any time, only a single owner is allowed.
    • The value is lost if the owner goes out of scope.
  • Scope is the range in which an item is valid.
  • Value types (primitives like int) are copied in memory. They don’t transfer ownership.
  • Ownership is transfered when the value doesn’t support copying, and is moved instead.