Rust Ownership

Rust Ownership

Ownership is how Rust achieves memory safety. It is one of the most unique and important features of the Rust programming language, but can be a bit complex and daunting for anyone new to Rust.

Ownership is important not only because it is responsible for memory safety, but also because it has a significant impact on how coding is done in Rust. It provides incredible benefits but its’ uniqueness is one of the main reasons that Rust is considered difficult for new programmers.

One of the challenges in learning the ownership system is that there are several interdependent concepts that need to be learned. It’s hard to discuss one without the others. For example ownership is highly dependent on the stack and the heap, but we can’t fully understand them without also understanding ownership in general.

For this reason, we will take a slightly unique approach to this topic. This main tutorial on ownership will present an overview of the concepts and rules related to ownership. This will provide a solid basis to help you understand ownership at a high but useful level.

The next several tutorials will be dedicated to doing deep dives on the essential topics related to ownership, gradually providing you with a clearer and more robust understanding of how ownership works in Rust. Ownership in Rust is considered a difficult subject so it’s important to take some time to learn this material. Don’t be discouraged if it takes a few sessions to learn this material and how the concepts fit together. However once we’re done we will have overcome a main obstacle in learning Rust.

Ownership Rules in Rust

The following rules are helpful for understanding how ownership works in Rust:

  1. Values have owners to which they are bound.
  2. When an owner is out of scope, the value is cleared from memory.
  3. A value can only have one owner at a time.
  4. Primitive types get copied; non-primitive types get moved.

We will cover these rules in the overview below in order to develop our understanding of ownership one step at a time.

An Overview of Ownership in Rust

The term ‘ownership’ refers to the fact that variable bindings ‘own’ what they are bound to.

For example, the variable ‘x’ below owns the value of ‘3’:

    let x = 3;

This is an example of Ownership Rule # 1: Values have owners to which they are bound.

The value of ‘3’ is saved into memory (on something called the stack), but only while ‘x’ is in scope.

The scope of ‘x’ is limited to the code block in which it is defined – the curly braces {}. If we try to call ‘x’ from outside its’ scope , we would get an error. This was previously covered in the tutorial on variable scope. When the compiler reaches the closing brace }, the memory holding the value of ‘x’ is freed.

This is an example of Ownership Rule # 2: When an owner is out of scope, the value is cleared from memory.

We have stated that the variable ‘x’ has ownership of the value that is bound to it (‘3’). This means that a second variable cannot point to the same position in memory. If we assign another variable to be equal to the value of ‘x’ then the Rust compiler will make a copy:

let x = 3; // The value of 3 is stored in memory
let y = x; // A copy of x is made, with a second position in memory allocated for it

Rust will never allow two variables to have ownership over the same value.

This is Ownership Rule # 3: A value can only have one owner at a time.

It may seem wasteful to make a copy of the value, but remember that all of the allocated memory will be cleared when ‘x’ and ‘y’ go out of scope.

Additionally, Rust only makes a copy when a small amount of memory is required to do so. Values that require larger amounts of memory don’t get copied but are instead moved and ownership is reassigned when assigned to a new variable.

This brings us to Ownership Rule # 4: Primitive types get copied; non-primitive types get moved.

Let’s say that the value of ‘x’ is a vector, rather than an integer:

let x = vec![1,2,3];

A vector is a non-primitive type. If we copy the value of ‘x’ to a new variable ‘y’, then the value gets moved instead of copied. This means that we can’t use ‘x’ anymore. The following will throw an error:

let x = vec![1,2,3];
let y = x;
println!("{:?}", x);

Standard Error:

     let x = vec![1,2,3];
           - move occurs because `x` has type `Vec<i32>`, which does not implement the `Copy` trait
     let y = x;
             - value moved here
     println!("{:?}", x);
                      ^ value borrowed here after move

Now that we have a basic understanding of ownership, we can build on these concepts to see how it impacts the way that Rust works at a deep level.