Module lessons (1/2)
The Rules of Ownership
Ownership is the most unique feature of Rust. It allows the language to guarantee memory safety without needing a Garbage Collector or forcing you to manually deallocate heap memory (like in C or C++).
The Three Rules of Ownership
Memory management in Rust is governed by three simple rules checked strictly by the compiler:
- Each value in Rust has a variable called its owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value is dropped automatically.
Stack vs Heap
To understand ownership, it is important to know where data resides:
- Stack: stores fixed-size data known at compile time (e.g., integers, booleans). Access is extremely fast.
- Heap: stores dynamic-size data unknown at compile time (e.g.,
String). Allocation is slower and requires a pointer stored on the Stack.
The Concept of "Move"
When we assign a heap-allocated variable to another, Rust moves (or transfers) ownership of the value, invalidating the first variable:
let s1 = String::from("ciao");
let s2 = s1; // Ownership of the text moves to s2. s1 is NO longer usable!
// println!("{}", s1); // COMPILATION ERROR! s1 is "borrow of moved value"
println!("{}", s2); // Valid!
This behavior avoids the "double free error" (trying to free the same memory twice when going out of scope), since only s2 will free the memory.
The Concept of "Clone" (Deep Copy)
If we explicitly need to copy the entire contents of a String (both the stack pointers and the actual text data in the heap), we can use the .clone() method. This completely duplicates the data on the heap, keeping both the original and the new variable valid, at the cost of a performance overhead for the new memory allocation:
let s1 = String::from("ciao");
let s2 = s1.clone(); // Deep copy. Both variables remain valid!
println!("s1: {}, s2: {}", s1, s2);
The Concept of "Copy"
For simple types stored entirely on the Stack (like integers i32, booleans bool, characters char), the assignment performs an actual automatic shallow copy, meaning both variables remain valid:
let x = 5;
let y = x; // Copies the value 5 onto the stack. Both variables are usable!
println!("x: {}, y: {}", x, y); // Valid!
Try it yourself
Declare a String s1 containing the text 'hello'. Assign s1 to s2 (so as to move ownership). Finally, print s2 using println!.
Show hint
Use `let s2 = s1;` to transfer ownership to `s2` and print it with `println!('{}', s2);`.
Solution available after 3 attempts
Declare an integer variable x with a value of 5. Assign x to y (performing a copy). Finally, print both x and y in the same println! statement.
Show hint
Do `let y = x;` and then print both values, e.g., `println!('{} {}', x, y);`.
Solution available after 3 attempts
Declare a String s1 with the value "Rust". Create a deep copy of s1 in a new variable s2 using the clone method. Finally, print both s1 and s2 separated by a space using the println! macro.
Show hint
Use `let s2 = s1.clone();` to deep copy the value of `s1` into `s2`, then print them with `println!("{} {}", s1, s2);`.
Solution available after 3 attempts