Move Semantics Exercises

Overview

These exercises cover Rust’s unique ownership model — the heart of memory safety without garbage collection.


move_semantics1 - Ownership & Mutability Inside Functions

Concept: Making a moved value mutable

fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
    let mut vec = vec;  // Shadow with mutable binding
    vec.push(88);
    vec
}

Key Takeaways:

  • When you pass a value to a function, ownership moves
  • The original variable can no longer be used
  • You can shadow the parameter to make it mutable inside the function

move_semantics2 - Clone to Keep Original

Concept: Using .clone() to avoid moving

#[test]
fn move_semantics2() {
    let vec0 = vec![22, 44, 66];
 
    // Clone creates a deep copy — vec0 stays valid
    let vec1 = fill_vec(vec0.clone());
 
    assert_eq!(vec0, [22, 44, 66]);  // vec0 still works!
    assert_eq!(vec1, [22, 44, 66, 88]);
}

Key Takeaway: .clone() creates a copy, so you don’t lose access to the original.

⚠️ Trade-off: Cloning has a performance cost — it copies all data.


move_semantics3 - Mutable Parameters

Concept: Declaring parameters as mutable

fn fill_vec(mut vec: Vec<i32>) -> Vec<i32> {
    //       ^^^ mut in parameter position
    vec.push(88);
    vec
}

Key Takeaway: You can put mut directly in the parameter to avoid shadowing.


move_semantics4 - Mutable References

Concept: Multiple mutable borrows (non-overlapping)

#[test]
fn move_semantics4() {
    let mut x = Vec::new();
    let y = &mut x;
    y.push(42);
    // y is no longer used after this point
 
    let z = &mut x;  // OK! y's borrow has ended
    z.push(13);
    assert_eq!(x, [42, 13]);
}

Key Takeaways:

  • Only one mutable reference at a time
  • But borrows end when no longer used (Non-Lexical Lifetimes)
  • This prevents data races at compile time!

move_semantics5 - Borrow vs Move

Concept: Choosing between borrowing and taking ownership

// Borrows — doesn't consume the String
fn get_char(data: &String) -> char {
    data.chars().last().unwrap()
}
 
// Takes ownership — consumes the String
fn string_uppercase(mut data: String) {
    data = data.to_uppercase();
    println!("{data}");
}
 
fn main() {
    let data = "Rust is great!".to_string();
 
    get_char(&data);      // Borrow with &
 
    string_uppercase(data);  // Move ownership
}

Decision Guide:

If you need to…Use
Read without changing&T (immutable borrow)
Modify temporarily&mut T (mutable borrow)
Consume/transform completelyT (take ownership)

The Ownership Rules

  1. Each value in Rust has exactly one owner
  2. When the owner goes out of scope, the value is dropped
  3. You can have either:
    • One mutable reference (&mut T)
    • Any number of immutable references (&T)
    • But not both at the same time!

Rust Book Reference