Most programs nowadays have garbage collectors that automatically look for unused memory; for some programs, you still have to allocate and free it by yourself. But Rust uses Ownership, another approach that handles the memory that the compilers check.

Quick review of stack and heap

Stack and Heap

Ownership rules

Move

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

    println!("{s1}, world!");

When we assign s1 to s2, the String data is copied, meaning we copy the pointer, the length, and the capacity that are on the stack. We do not copy the data on the heap that the pointer refers to.

To ensure memory safety, after the line let s2 = s1;, Rust considers s1 as no longer valid. Therefore, Rust doesn’t need to free anything when s1 goes out of scope. Check out what happens when you try to use s1 after s2 is created; it won’t work:

You’ll get an error because Rust prevents you from using the invalidated reference.

The concept of copying the pointer, length, and capacity without copying the data probably sounds like making a shallow copy. But because Rust also invalidates the first variable, instead of being called a shallow copy, it’s known as a move.

Clone

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

println!("s1 = {s1}, s2 = {s2}");

If we do want to deeply copy the heap data of the String, not just the stack data, we can use a common method called clone. We’ll discuss method syntax in Chapter 5, but because methods are a common feature in many programming languages, you’ve probably seen them before.

Stack-Only Data: Copy