Skip to content
2024-10-26

002 - Stack vs Heap, Moves, and Clones

Stack vs Heap, Moves, and Clones

Why this matters

Python hides most allocation details behind references and a garbage collector. Rust makes ownership and allocation behavior explicit, which is key to predictable performance.

Learning goals

By the end, you should be able to:

  • Explain stack vs heap allocation in Rust.
  • Predict when assignment causes a move.
  • Choose intentionally between borrow, move, and clone.

Python baseline

items = ["a", "b"]
other = items
other.append("c")
print(items)  # ["a", "b", "c"]

Both names reference the same underlying object.

Rust mental model

  • Fixed-size values (like i32) are typically copied on assignment.
  • Heap-owning values (String, Vec<T>) are moved by default.
  • clone() performs explicit deep copy for owned data.

Rust examples

Copy type (i32)

let a = 10;
let b = a; // copy
println!("a = {}, b = {}", a, b);

Move type (String)

let s1 = String::from("rust");
let s2 = s1; // move
// println!("{}", s1); // compile error: moved
println!("{}", s2);

Explicit clone

let s1 = String::from("rust");
let s2 = s1.clone();
println!("{} / {}", s1, s2);

Runnable end-to-end example

fn takes_ownership(text: String) {
    println!("owned: {}", text);
}

fn borrows(text: &str) {
    println!("borrowed: {}", text);
}

fn main() {
    // stack-allocated copy type
    let x = 7;
    let y = x;
    println!("x = {}, y = {}", x, y);

    // heap-allocated move type
    let language = String::from("Rust");
    borrows(&language);

    // move into function
    takes_ownership(language);
    // borrows(&language); // would fail: moved

    // explicit clone if both are needed
    let original = String::from("memory");
    let cloned = original.clone();
    println!("original = {}, cloned = {}", original, cloned);
}

Common pitfalls

  • Cloning too early everywhere (hidden allocation cost).
  • Fighting moves instead of borrowing with &T.
  • Assuming assignment duplicates heap data.

Quick practice

  1. Replace one clone() with a borrow and keep behavior the same.
  2. Write fn len_of(s: &String) -> usize and call it without moving the string.

Recap

Rust separates copy, move, and clone clearly. This explicitness is the foundation for both safety and performance.

Next lesson: 003 - Borrowing Rules in Real Functions.

Join the Journey Ahead!

If you're eager to continue this learning journey and stay updated with the latest insights, consider subscribing. By joining our mailing list, you'll receive notifications about new articles, tips, and resources to help you seamlessly pick up Rust by leveraging your Python skills.

Other articles in the series