Ownership: Rust's Killer Feature
Ownership is the most unique concept in Rust - the reason it achieves memory safety without garbage collection. Three rules govern ownership, enforced entirely at compile time with zero runtime cost.
The Three Rules
- Rule 1: Each value has exactly one owner
- Rule 2: Only one owner at a time
- Rule 3: When the owner goes out of scope, the value is dropped
fn main() {
let s = String::from("hello"); // s owns "hello"
println!("{}", s);
} // s goes out of scope -> drop() called -> memory freed
fn scope_demo() {
let x = 5; // x is valid from here
{
let y = 10; // y valid here
println!("{} {}", x, y);
} // y dropped here
// println!("{}", y); // ERROR: y not in scope
println!("{}", x); // x still valid
}Move Semantics
When you assign a heap-allocated value to another variable, ownership moves. The original variable becomes invalid:
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 MOVED to s2
// println!("{}", s1); // ERROR: s1 was moved!
println!("{}", s2); // OK: s2 is the owner
// Primitives are COPIED, not moved:
let x = 5;
let y = x;
println!("{} {}", x, y); // Both valid - i32 implements Copy
// Explicit clone for heap types:
let a = String::from("hello");
let b = a.clone(); // Deep copy - new heap allocation
println!("{} {}", a, b); // Both valid
}
// Ownership through functions:
fn takes_ownership(s: String) {
println!("{}", s);
} // s dropped here
fn returns_ownership() -> String {
String::from("returned") // Ownership moves to caller
}
fn main2() {
let s = String::from("hi");
takes_ownership(s); // s moved in
// s is no longer valid here
let r = returns_ownership();
println!("{}", r); // r owns the returned String
}🎯 Practice
- Create a String, move it to a function. Try to use the original - read the compiler error.
- Fix the above by returning the String from the function
- Demonstrate that i32 can be used after assignment (Copy semantics)
- Use clone() to allow using both the original and a copy
🎉 Key Takeaways
- One owner at a time; dropping owner frees memory automatically
- Heap types (String, Vec) move on assignment - original invalid
- Stack types (i32, bool, char, tuples of Copy types) are copied
- clone() creates explicit deep copies when you need two owners