Rust Ownership
Ownership is the set of compile-time rules that lets Rust manage memory safely without a garbage collector, and it underlies almost every other language feature.
The Three Rules of Ownership
Rust's ownership model rests on three simple rules enforced entirely at compile time: each value in Rust has a single variable that is its owner; there can only be one owner at a time; and when the owner goes out of scope, the value is dropped automatically. These rules eliminate entire categories of bugs — double frees, use-after-free, and dangling pointers — without needing a runtime garbage collector.
Move Semantics
For heap-allocated types like `String` or `Vec<T>`, assigning a value to a new variable or passing it to a function does not copy the underlying data. Instead, Rust performs a move: ownership transfers to the new binding, and the original variable is invalidated by the compiler. This is different from languages like Python or JavaScript, where assignment either copies or creates a shared reference that both names can still use.
A Move Invalidates the Original Binding
fn main() {
let original = String::from("ownership");
let moved = original; // `original` is moved into `moved`
// println!("{}", original); // this line would fail to compile
println!("{}", moved); // only `moved` is valid now
}Copy Types Are the Exception
Simple, fixed-size types stored entirely on the stack — integers, floats, booleans, characters, and tuples composed only of such types — implement the `Copy` trait. Assigning or passing a `Copy` type duplicates the bits instead of moving them, so both the original and the new binding remain valid and usable.
Copy Types Do Not Move
fn main() {
let x: i32 = 42;
let y = x; // `x` is copied, not moved
println!("x = {}, y = {}", x, y); // both remain valid
}Ownership and Function Calls
Passing a non-`Copy` value into a function moves ownership into that function's parameter. Once the function returns, unless it explicitly returns the value back to the caller, the value is dropped at the end of the function body. This is a frequent surprise for newcomers, and it is the primary motivation for borrowing, covered in the next lesson.
Ownership Moves Into a Function
fn take_ownership(text: String) {
println!("inside function: {}", text);
} // `text` is dropped here
fn main() {
let message = String::from("transferred");
take_ownership(message);
// println!("{}", message); // fails: value was moved
}- Every heap-allocated value has exactly one owning variable at any given time.
- Assigning a non-Copy value to a new variable moves it; the old variable becomes unusable.
- Passing a non-Copy value into a function moves it into that function's scope.
- Returning a value from a function moves ownership back out to the caller.
- Calling `.clone()` explicitly duplicates heap data, producing two independent owners.
Returning Ownership
A function can hand ownership back to its caller simply by returning the value. This is a common pattern for functions that take ownership of an argument temporarily, transform it, and then give the result back so the caller retains a usable value.
Taking and Giving Back Ownership
fn add_exclamation(mut text: String) -> String {
text.push('!');
text
}
fn main() {
let phrase = String::from("rust is fun");
let phrase = add_exclamation(phrase); // ownership returns here
println!("{}", phrase);
}Exercise: Rust Ownership
What happens when you assign a `String` variable to another, e.g. `let s2 = s1;`?