Mastering Rust's Ownership: The Key to Memory Safety and Efficiency
Tags: rust, programming, javascript, beginners
Published: 2023-08-14 08:29:19

The ownership mechanism in Rust is a unique feature that sets it apart from other mainstream programming languages. It's what makes Rust both memory-safe and efficient while avoiding the need for a garbage collector, a common feature in languages like Java, Go, and Python. Understanding ownership is crucial for becoming proficient in Rust. In this article, we will explore the following topics: 

1. Stack and Heap 
1. Variable Scope
1. Ownership
1. Borrowing (Referencing) 
1. Mutable Reference 

__Note:__ This article is beginner-friendly. Familiarity with Rust's basic syntax, variable declaration within functions, and basic data types is assumed. If you need a quick refresher, you can check out this [guide](https://dev.to/charles_lukes/introduction-to-common-programming-concepts-in-rust-1c10).

## Stack AND Heap
Memory in Rust can be divided into two main parts: the stack and the heap. Both of these are available during both compile time and runtime and serve different purposes. Understanding their mechanics is crucial for writing efficient Rust code. 

### Stack
The stack is used to store data with known sizes at compile time. The size of data stored on the stack is fixed and cannot be changed during runtime.

The stack operates using a First In, Last Out (FILO) mechanism, similar to stacking plates. The first plate added is the last one removed.

Rust automatically adds primitive types to the stack:

```rust 
fn main() {

    let num: i32 = 14;
    let array = [1, 2, 4]; 
    let character = 'a'; 
    let tp = (1, true); 
    
    println!("number = {}, array = {:?}, character = {}, tp = {:?}", num, array, character, tp); 
}

```

The stack layout for the `main` method would be as follows:

| Address  | Name | Value |
| ------  | ------ | ---- |
| 3  | tp | (1, true)  | 
| 2  | character | 'a'  | 
| 1  | array | [1,2,4]  | 
| 0  | num |  14 | 

In this table, the `num` variable, being the first added, will be the last removed when the function scope ends.

Note: Address column uses numbers as representation, ideally addresses in computers are not just numbers. 


### Heap
The heap is used for data with sizes unknown at compile time. Types like `Vec`, `String`, and `Box` are stored on the heap automatically.

```rust 
fn main() {

    let num: i32 = 14;
    let new_str = String::new(); 
}
```

The memory mapping for data on the heap is as follows:

__STACK MEM__

| Address  | Name | Value |
| ------  | ------ | ---- |
| 1  | new_str | ptr1  | 
| 0  | num |  14 | 

__HEAP MEM__

| Address | Name | Value |
| ------  | ------ | ---- |
| ptr1 | | ""  | 

When storing data on the heap, a pointer (like `ptr1`) is first stored on the stack. This pointer then references the actual data on the heap. This additional step makes heap access slower compared to stack access.

With stack and heap mechanisms understood, let's delve into variable scope. 

### Variable Scope
Variable scope defines the range in code where a variable is valid. In Rust, curly braces create blocks, and variables within these blocks are scoped to them.

```rust
fn main() {
// everything here is scoped to this block
 let hello = "Hello World";
// ...
}
```
Variables within a block cannot be accessed outside that block:

```rust
fn main() {
 let hello = "Hello World"; 
  
  // inner block
  {
    let num = 20;
  }

 // This will fail to compile
 println!("{} {}", hello, num);
}
```
From the above you can't use `num` outside the inner block because it is scoped only to the inner block. 

Rust runs functions from top to bottom, allocating stack and heap memory as needed. When the scope ends, Rust calls the `drop` method internally to deallocate memory, a crucial concept for understanding Rust's approach to memory management.

## Ownership
Here we come to the heart of the article, where we explore ownership and its impact on Rust programming. Remember these ownership rules as we proceed:

### Ownership Rules 
1. There can only be one owner of a value at a time. 
1. Each value has an owner. 
1. If the owner goes out of scope the value is dropped. 

Lets see what this mean below:

```rust
fn main() {
    let array = vec![1,2,3]; 
    let title = String::new(); 

    let new_array = array;
    let new_title = title; 

    println!("array {:?} title {} new_array {:?} new_title {}", array, title, new_array, new_title);
 }
``` 

![move bug](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5spdyhmlz8ghvk3kfa0g.png)
The compilation error "borrow of moved value" arises because both `array` and `title` are being owned by multiple variables simultaneously. This violates the ownership rules.

Rust's memory layout for the code above is as follows:

__STACK MEM__

| Address  | Name | Value |
| ------  | ------ | ---- |
| 3  | new_title | ptr2Copy  | 
| 2  | new_array | ptr1Copy  | 
| 1  | title | ptr2  | 
| 0  | array |  ptr1 | 

__HEAP MEM__

| Address | Name | Value |
| ------  | ------ | ---- |
| ptr2 and ptr2Copy | | ""  | 
| ptr1 and ptr1Copy | | vec![1,2,3]  | 

As seen from above because vectors and strings are non fixed size types Rust allocates them to the heap and can be accessed via pointer from the stack. 

The `ptr1Copy` pointer is a copy of `ptr1` pointer and the `ptr2Copy` pointer is a copy of `ptr2` pointer. 

When Rust is done it then calls the `drop` method, the `drop` method walks through the stack remember **FILO** we explained above? It starts by removing the last item added to the stack which is `new_title` then finds out it has a pointer(`ptr2Copy`) goes to the heap memory via that pointer and removes the string value. It continues to the next element on the stack `new_array` which also has a pointer `ptr1Copy` and removes the vector from the heap memory too. It again continues to the next which is `title` and has a pointer `ptr2` but when it follows the pointer it realises that the data no longer exits as it has been removed via `ptr2Copy` this why it throws error for `title` the same thing goes for the `array` variable. 


The Ownership rules apply only to Rust types on the heap. Primitive types on the stack don't follow these rules:

```rust
fn main() {
    let array = [1,2,3]; 
    let title = "hello"; 
 
    let new_array = array;
    let new_title = title; 
 
    println!("array {:?} title {} new_array {:?} new_title {}", array, title, new_array, new_title);
 }
```

![primitive copy types](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i2zjs2u76y3g1w4pva5h.png)

For readers familiar with other languages, reassigning values to new variables while they're on the heap might seem intuitive. How do we achieve this in Rust?

We can use the `.clone` method, which duplicates the heap value and stores it in a new location on the heap:

See below: 
```rust
fn main() {
    let array = vec![1,2,3]; 
    let title = String::new(); 
    
    let new_array = array.clone();
    let new_title = title.clone(); 
    
    println!("array {:?} num {} new_array {:?} new_num {}", array, title, new_array, new_title);
}
``` 
From the above the code works now, let's see how the translation is done on the stack and heap
 
__STACK MEM__

| Address  | Name | Value |
| ------  | ------ | ---- |
| 3  | new_title | ptr2Copy  | 
| 2  | new_array | ptr1Copy  | 
| 1  | title | ptr2  | 
| 0  | array |  ptr1 | 

__HEAP MEM__

| Address | Name | Value |
| ------  | ------ | ---- |
| ptr2Copy | | ""  | 
| ptr1Copy | | vec![1,2,3] | 
| ptr2 | | ""  | 
| ptr1 | | vec![1,2,3]  | 

While this approach works, it might result in unnecessary memory consumption and inefficiencies. 

## Borrowing

In Rust, borrowing (or referencing) allows us to use values without taking ownership. To reference a value, we use the `&` sign.

```rust
fn main() {
    let hello = String::from("hello");
    let another_hello = &hello;

    let arr = vec![1];
    let another_arr = &arr;

    println!("hello = {} another_hello = {} {:?} {:?}", hello, another_hello, another_arr, arr)
  }
``` 
By referencing values, we can read data without copying it, avoiding unnecessary memory overhead.

This is also applicable for functions parameters. Function parameters have their own scope, passing a value to it will automatically move the ownership to the parameters. 

```rust 
fn main() {
    let name = String::from("John");
    say_hello(name);
    println!("{}", name)
}

fn say_hello(name: String) {
    println!("hello {}", name)
}
```

![ownership in function params](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/afamvmau0387niloze2w.png)
The above failed because the ownership of value "John" was moved to the function params. 

We can resolve this error by borrowing from the name owner, see below

```rust 
fn main() {
    let name = String::from("John");
    say_hello(&name);
    println!("{}", name)
}

fn say_hello(name: &String) {
    println!("hello {}", name)
}
```
We don't have anymore errors, looks good so far. 

All these while we've only read the value from the owner what if we want to update that value and still not take ownership? 

## Mutable References

Mutating values in Rust requires the `mut` keyword, and to mutate a value via reference, we use the `&mut` keyword. 

```rust
fn main() {
  let mut name = String::from("John");

  full_name(&mut name);

  println!("Your full name is: {}", name);
}

fn full_name(name: &mut String) {
  name.push_str(" Doe");
}
```
From above the `full_name` function is able to update the name value without taking ownership of the value. 

One thing to always remember is at any given time, you can have either one mutable reference or any number of immutable references.
So the following code will fail to compile: 

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

    let r1 = &mut s;
    let r2 = &mut s;

    println!("{}, {}", r1, r2);
}
```

![multiple mutable reference fail](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7a49wzr6jeg34d5oxl8s.png)

Even if we are just reading the value `hello` as far as we have a single mutable reference rust complains. The below still fails to compile

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

    let r1 = &s;
    let r2 = &s;
    let r3 = &mut s;
   
    println!("{}, {}, {}", r1, r2, r3);
}
```

The reason for this is to avoid data inconsistencies because rust cannot guarantee at what point the reading or data change will happen. 

We can make the above work with scope.

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

    // inner scope 
    {
     let r1 = &mut s;
     println!("{}", r1);
    }

    let r2 = &mut s;
    println!("{}", r2);
   
}
```

Also we can read all read references and be done with them before mutating. 
```rust 
fn main() {
 let mut s = String::from("hello");

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    println!("{} and {}", r1, r2);
    // variables r1 and r2 will not be used after this point

    let r3 = &mut s; // no problem
    println!("{}", r3);
}
```
Rust enforces us to think of our code properly to avoid writing buggy code that are hard to fix. 

## Conclusion
In the world of programming languages, Rust stands out as a remarkable choice due to its ownership mechanism. This mechanism, while initially challenging to grasp, is the cornerstone of Rust's memory safety and efficiency. By strictly adhering to rules that enforce single ownership, Rust mitigates the risk of memory leaks, data inconsistencies, and other common programming pitfalls.

Through this article, we've explored the intricate workings of Rust's ownership concept. We began with a foundational understanding of the stack and heap, the pillars that support memory allocation. The stack, efficient for fixed-size data, and the heap, accommodating dynamic data, together enable Rust to balance performance and flexibility.

The significance of variable scope became evident as we delved into the importance of block-level scoping. Rust's rigorous scope management not only simplifies code but also assists in automating memory deallocation through the drop mechanism.

Ownership, the core of Rust's memory management philosophy, teaches us the invaluable lesson that only one entity can own a value at any given time. This principle may initially seem restrictive, but it's an essential safeguard against data races and memory issues.

We learned how to navigate these ownership rules by utilizing borrowing, a mechanism that allows controlled access to values without compromising ownership. We saw how references (`&`) and mutable references (`&mut`) facilitate this process, enabling both reading and manipulation without sacrificing data integrity.

In a landscape where memory safety is often sacrificed for convenience, Rust shines as a language that compels developers to adopt best practices. While the ownership model can be challenging, mastering it will make you a more disciplined and effective Rust programmer.

As you continue your journey with Rust, remember that ownership isn't just a technicality; it's a mindset. Embrace it, let it guide your coding decisions, and you'll unlock the true potential of this language. Rust's ownership is not merely a hurdle to overcome; it's a superpower to wield responsibly.

So, as you write your Rust code, keep these ownership principles in mind, and you'll be well on your way to creating robust, efficient, and reliable software that stands the test of time.