Learning Rust 🦀: 07 - Ownership: Memory Dynamics
Tags: learning, rust, programming, development
Published: 2023-07-28 13:03:35

In the last article, I said that I'd split the Rust Ownership topic over 2 articles. But this will be a bit hard in order for the articles to be short and concise. So, I'll split the Ownership topic over 3 or maybe 4 articles. 

>#### ⚠️ Remember!
You can find all the code snippets for this series in its [accompanying repo](https://github.com/FadyGrAb/rust-learning-series)  
If you don't want to install Rust locally, you can play with all the code of this series in the official [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021) that can be found on its official page. But for this one, you need a `cargo` installation on your machine (or a VM).

> ⚠️⚠️ The articles in this series are loosely following the contents of "The Rust Programming Language, 2nd Edition" by Steve Klabnik and Carol Nichols in a way that reflects my understanding from a Python developer's perspective.

⭐ I try to publish a new article every week (maybe more if the Rust gods 🙌 are generous 😁) so stay tuned 😉. I'll be posting "new articles updates" on my [LinkedIn](https://www.linkedin.com/in/fady-abdelmassih) and [Twitter](https://twitter.com/fadyGrAb).

## Table of Contents:
- [The String type](#the-string-type)
- [Memory and allocation](#memory-and-allocation)
- [The Move and The Copy](#the-move-and-the-copy)
- [The Clone](#the-clone)

### The String type:

So far, we have been using types with known sizes during compile time and those get stored in the *Stack*. In order to demonstrate further Rust's Ownership, we have to use a type that gets stored in the *Heap*. The *String* type is one of those types. In general, a pointer to the *Heap* memory block that is "allocated" to a String variable and its length are stored in the *Stack* and the actual data is stored in the *Heap*. A String variable can be of unknown size at compile time and it can be mutable because - as pointed out - that it reserves a place in the memory that can grow and shrink unlike the string literals which are hardcoded in the final executable and therefore with known size hence they are stored in the *Stack*.
```rust
let mut hello = String::from("Hello"); // A new String variable using the "from" method from the String namespace.
hello.push_str(", World!"); // Appends a string to the same String variable.
println!("{hello}");
```

### Memory and allocation:
For type like the String which is of unknown size at compile time, we have to do two things:
1. Request the needed memory at runtime.
2. Release this memory when we are done using the *String*.

The *Request* part is done by us. We did that when we called the "String::from" method in the previous example. The second part is where Rust's Ownership comes to play. For languages that use "garbage collection" mechanisms, the GC (Garbage Collector) takes care of this. For other languages (other than Rust), the developer has to do both the request and release parts with the latter proving to be challenging. As forgetting about releasing the memory will waste it. And doing it too soon will result in invalid variables. Also doing it twice is a bug. So in short, 
> We need to pair exactly one allocate with one release.  

Rust, however, releases the requested memory on your behalf. It does so whenever a variable goes out of scope.

### The Move and the Copy:
The memory release rule seems simple enough but imagine this scenario: 
```rust
let s1 = String::from("String Value");
let s2 = s1;
println!("S2: {s2}");
println!("S1: {s1}");
```
For *heap stored types* (if we can call them that), a statement like `let s2 = s1;` doesn't duplicate the data of `s1` in the heap and associate the new copy with `s2` as this would be expensive at runtime (imagine a very very large String that consumes a large portion of the memory get duplicated!). Instead, Rust keeps the *Heap data* intact and just creates another pointer to it in `s2`. This presents a problem, Rust releases the allocated memory once the variable goes out of scope. So, suppose that `s1` goes out of scope first, then Rust releases the *heap data*. Soon after, `s2` goes out of scope, then Rust supposedly tries to release the *heap data* which was already released when `s1` went out of scope. This is the "double release" problem mentioned in the previous section.  
What Rust does to enforce memory safety for situations like these, it invalidates `s1` in the previous example after `let s2 = s1`. So `println!("S1: {s1}")` will cause a compile error.
> It is said that `s1` has been **MOVED** to `s2`.  

But this isn't how Rust behaves with simple types like integers! For type with known size at compile time, Rust doesn't move the variable in case of an assignment statement like the previous one. It **COPIES** the variable as it is already stored in the *Stack* which is easier to copy. So, the following example is valid:
```rust
let n1 = 10;
let n2 = n1;
println!("n1: {n1}"); // is valid.
println!("n2: {n2}");
```
> It is said that `n1` has been **COPIED** into `n2`.

### The Clone:
But what if duplicating *Heap data* is just what we want and we don't want to *move* variables? Rust has the "clone" method for that:
```rust
let s3 = String::from("Clone");
let s4 = s3.clone();
println!("s3: {s3}"); // is valid.
println!("s4: {s4}");
```
This method clones the *Heap data* which is - as mentioned earlier - can be very expensive at runtime so use it carefully.  

I'll have to stop here to keep it short. In the next article, we will discuss Ownership and Functions! See you then 👋.