Rust Move Semantics

Rust’s memory model is built on the idea of ownership. When you assign a value from one variable to another, you might think it’s just a “copy.” But in Rust, for many data types, it’s actually a move.

This move system is what keeps your programs memory-safe without garbage collection.

What Does “Move” Mean? #

In Rust, when you assign or pass a value to another variable or function, the ownership of that value is transferred (moved).

After the move:

  • The new variable becomes the owner.
  • The old variable can no longer be used.

This ensures that there’s always exactly one owner of any piece of data, preventing double frees or dangling pointers.

Example: Moving a String #

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;  // ownership moves from s1 to s2

    println!("{}", s2);  // ✅ works
    // println!("{}", s1);  // ❌ ERROR: s1 is no longer valid
}
Code language: Rust (rust)

Why does this happen? #

  • String stores its text on the heap.
  • If Rust allowed both s1 and s2 to point to the same heap memory, freeing them twice would cause a crash.
  • So, Rust enforces the move rule.

But What About Integers? #

fn main() {
    let x = 10;
    let y = x;  // x is copied, not moved

    println!("x = {}, y = {}", x, y); // ✅ works
}
Code language: Rust (rust)
  • i32, bool, char, and other primitive types are stored entirely on the stack.
  • Copying them is cheap and safe, so Rust automatically gives them the Copy trait.
  • That’s why x is still valid after assigning y = x.

Move in Function Calls #

fn main() {
    let name = String::from("Alice");
    greet(name);  

    // println!("{}", name); // ❌ ERROR: name was moved into the function
}

fn greet(person: String) {
    println!("Hello, {}!", person);
}
Code language: Rust (rust)
  • When you pass name to greet, its ownership moves into the function.
  • After the call, name is no longer valid in main.

Borrowing Instead of Moving #

What if you want to use the value without giving up ownership? That’s where borrowing (&) comes in.

fn main() {
    let name = String::from("Alice");
    greet(&name);  

    println!("Still own it: {}", name); // works
}

fn greet(person: &String) {
    println!("Hello, {}!", person); // borrowed, not moved
}
Code language: Rust (rust)

In this example:

  • The greet function borrowed name.
  • The main function still owns name after the greet function finishes.

Summary #

  • Move semantics transfer ownership between variables.
  • Heap-allocated types like String are moved by default.
  • Primitive types (like integers) are copied instead of moved.
  • Function arguments can move values into functions.
  • Use borrowing (&) to let functions access values without taking ownership.

This system keeps memory management safe and bug-free — no dangling pointers, no double frees, and no garbage collector needed.

Was this Helpful ?