Ownership in Rust
Tags: rust, programming, tutorial, learning
Published: 2024-02-20 13:59:56

## Ownership in Rust

Rust has a concept of ownership that is unique among programming languages. It is a key feature of the language that allows it to be both safe and fast. In this lesson, we will explore what ownership is, how it works, and why it is important.

If you prefer a video version
{% youtube 9VBLOwmNE1g %}

<!-- <iframe width="905" height="510" src="https://www.youtube.com/embed/9VBLOwmNE1g" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe> -->

All the code is available on [GitHub](https://youtu.be/9VBLOwmNE1g) (link available in the video description)


## In this lesson

In this lesson, we will cover the following topics:
- What is ownership?
- The `Stack` and the `Heap`
- Ownership rules
- The `String` type
- Memory allocation and the `Drop` function
- The `Move` trait
- The `Clone` trait
- The `Copy` trait
- Final Recap

## What is Ownership?

[![Ownership in Rust - Rust programming tutorial](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/28jojjsjaa5e91pidjc9.png)](https://youtu.be/9VBLOwmNE1g)


Ownership is a distinct feature of Rust, enabling safe and efficient memory management. 

In Rust, each value has a sole owner responsible for its disposal when it's not needed, eliminating the need for garbage collection or reference counting. 

This lesson will be about ownership through examples centered on Strings, a common data structure.

⚠️ Before we proceed, it's important to understand that Ownership is not something that runs in the background or adds an overhead to the program. It's a concept that is enforced at compile time, and it's a key feature of the language that allows it to be both safe and fast.

## The Stack and the Heap

Understanding the stack and heap is essential in Rust due to its unique memory management through ownership. 

### The Stack

[![The Stack and the Heap](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mr8309obpcnxrppvccud.png)](https://youtu.be/9VBLOwmNE1g)


The Stack, used for fixed-size data, provides quick access. 

It has some methods to manage the data, like `push` and `pop`, and it's very fast.

All the data stored on the stack must have a known, fixed size at compile time, making it ideal for small, fixed-size data.

Data with an unknown size or a size that might change at runtime is stored on the heap, which is slower than the stack due to its dynamic nature.

### The Heap

The Heap, on the other hand, is used for data whose size might change or cannot be determined until runtime.

[![The Stack and the Heap](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ffqhlvd5glske35q1l2.png)](https://youtu.be/9VBLOwmNE1g)

The Heap is less organized than the stack, and it's slower to access data stored on it.

The momory allocated on the heap is not managed by the Rust compiler, but by the programmer. This is why Rust has a strict ownership model for managing the memory safely and efficiently, preventing memory leaks, and ensuring data is cleaned up properly.

Note: the pointer to the data is stored on the stack, but the data itself is stored on the heap.

### Operations on the Stack and the Heap

Here is a recap of the operations on the stack and the heap:

- The stack is fast and efficient, but it can only store data with a known, fixed size at compile time.

- The heap is slower and less organized, but it can store data with an unknown size or a size that might change at runtime.

- The memory allocated on the heap is not managed by the Rust compiler but by the programmer.

- Functions like `push` and `pop` are available for the stack, but not for the heap.

- The pointer to the data is stored on the stack, but the data itself is stored on the heap.

[![The Stack and the Heap](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b3s3080rmv5ffkbndnzi.png)](https://youtu.be/9VBLOwmNE1g)

## Ownership purpose and  Rules

Ownership's primary purpose is to manage the data stored on the Heap, ensuring it's cleaned up properly and preventing memory leaks.

To achieve this:
- it keeps track of what code is using what data on the heap
- it minimizes the amount of duplicate data on the heap
- it cleans up the data on the heap when it's no longer needed

Rust has a few ownership rules:

1. Each value in Rust has a variable that's its owner.
2. There can only be one owner at a time.
3. When the owner goes out of scope, the value will be dropped.

Let's see an example.

## Variable scope

In Rust, a variable is only valid within the scope it was declared.

Even when a variable is stored on the Stack, it is only valid within the scope it was declared.

```rust
fn main() {
{                      // s is not valid here, it’s not yet declared
        let s = "hello";   // s is valid from this point forward

        // do stuff with s
    }                      // this scope is now over, and s is no longer valid
}
```

In the example above, `s` is the owner of the string "hello". When `s` goes out of scope, the string will not be valid anymore.

## The String type

In a previous lesson, we covered simple, fixed-size data types in Rust. 

Now, we'll explore the String type, a compound type allocated on the heap, with a detailed look planned for a future lesson.

We've touched on string literals, which are immutable and embedded in the program. Unlike these, the String type is mutable and heap-allocated, allowing it to hold text of variable length, unknown at compile time.

```rust
let s = String::from("hello");
```

This kind of string can be mutated:

```rust
    let mut s = String::from("hello");

    s.push_str(", world!"); // push_str() appends a literal to a String

    println!("{}", s); // This will print `hello, world!`
```

So, what’s the difference here? 

The key difference lies in their memory handling: String can be mutated due to its dynamic memory allocation on the heap, while literals, stored in fixed memory, cannot be changed.

## Memory Allocation and the Drop Function

```rust
    {
        let s = String::from("hello"); // s is valid from this point forward

        // do stuff with s
    } // this scope is now over, and s is no longer valid
```

When a variable goes out of scope, Rust calls a special function for us. 

This function is called `drop`, and it’s where the author of String can put the code to return the memory. 

Rust calls drop automatically at the closing curly bracket.

## The Move Trait

If we try to do something like that:

```rust
    let s1 = 5;
    let s2 = s1;

    // print s2
    println!("{}, ", s2);

    // print s1
    println!("{}, ", s1);
```

This code is valid and it will print `5` twice.

If we do something like that:

```rust
    let s1 = "hello";
    let s2 = s1;

    // print s2
    println!("{}, ", s2);

    // print s1
    println!("{}, ", s1);
```

This code is also valid, and it will print `hello` twice.

But if we type something like that:

```rust
    let s1 = String::from("hello");
    let s2 = s1;

    // print s2
    println!("{}, ", s2);

    // print s1
    println!("{}, ", s1);
```

This code will throw a compile-time error! 

Why? Because Rust considers `s1` to be invalid after the assignment to `s2`. This is because Rust has a special trait called `Move` that is implemented for the `String` type.

Below is a schema of what happens. We might think that when we use the `=` operator, we copy the pointer or the whole data again, but this is not what happens: We `MOVE` the pointer to the new variable (that's why it's called Move trait).

[![The Move Trait](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/srnpcsspxgeqczuts5a9.png)](https://youtu.be/9VBLOwmNE1g)


The `s1` variable is no longer valid after the assignment to `s2`.

To 'fix' our code, we can use the `clone` method (as suggested by the compiler):

```rust
    let s1 = String::from("hello");
    let s2 = s1.clone();

    // print s2
    println!("{}, ", s2);

    // print s1
    println!("{}, ", s1);
```

## The Clone Trait

If we call the clone method, what happens is exactly what is shown in the schema below: we copy the data and the pointer to the new variable.

[![The Clone Trait](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zvij8e3m3bgt7lmmxykg.png)](https://youtu.be/9VBLOwmNE1g)

So why do  we need the clone? Because for the variables stored on the heap, Rust, by default, does not copy the data when we assign a variable to another. It only copies the pointer to the data. This is because copying the data would be expensive in terms of performance.

So if we use the `clone` method or we read someone else using the `clone` method in their code, we know that this was intentional and that the author of the code wanted to copy the data and the pointer to the new variable.

## Stack-Only Data: Copy

So now you might be wondering: "Hey, if we need to use the `clone` method to copy the data and the pointer to the new variable, why didn't we need to use the `clone` method when we copied the integer or the string literal?"

```rust
    let x = 5;
    let y = x;

    println!("x = {}, y = {}", x, y);
```

[![The Copy Trait](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6rdkmjp7shcs0gp3bsba.png)](https://youtu.be/9VBLOwmNE1g)

Integers, being simple fixed-size values, are stored directly on the stack, enabling Rust to copy their bits from one variable to another without invalidation upon scope exit. This is because there's no distinction between deep and shallow copying for such types, making bit-copy safe.

Rust utilizes the Copy trait for types like integers that reside on the stack, ensuring automatic data duplication without runtime overhead. However, types with the Drop trait, which require special cleanup, cannot implement Copy. Adding Copy to such types would lead to compile-time errors, as it contradicts the need for controlled resource release.

Types eligible for the Copy trait include simple scalar values that don't need dynamic allocation or aren't complex resources. 

Examples include 
- all integer types (e.g., u32)
- the Boolean type (bool)
- floating-point types (e.g., f64)
- the character type (char)
- tuples containing only Copy types (e.g., (i32, i32)). 

Conversely, tuples with non-Copy components, like (i32, String), do not implement Copy.

## Final Recap

In this lesson, we covered the following topics:

- What is ownership?
- The `Stack` and the `Heap`
- Ownership rules
- The `String` type
- Memory allocation and the `Drop` function
- The `Move` trait
- The `Clone` trait
- The `Copy` trait

In the next lesson, we will cover references and borrowing in Rust.

If you prefer a video version
{% youtube 9VBLOwmNE1g %}

<!-- <iframe width="905" height="510" src="https://www.youtube.com/embed/9VBLOwmNE1g" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe> -->

All the code is available on [GitHub](https://youtu.be/9VBLOwmNE1g) (link available in the video description)

You can find me here: [Francesco](https://francescociulla.com)