Ownership is Rust’s core memory safety system. Each value in Rust has a single owner, when ownership moves to another variable, the original one becomes invalid. This ensures memory safety without needing a garbage collector.
If there is one concept you must understand to write real Rust programs, it is ownership. Unlike JavaScript, Rust does not have a garbage collector running in the background. Instead, it uses a set of rules around ownership to automatically manage memory and prevent bugs like use-after-free or double-free errors.
In this post, I will walk you through how ownership works in Rust, how it affects your variables, and what you need to keep in mind when writing or reading Rust code.
If you have not gone through Reading Input from the Console or Variables and Mutability yet, you may want to review those first.
What Is Ownership?
Ownership in Rust is based on three rules:
- Each value in Rust has a single owner.
- When the owner goes out of scope, the value is dropped.
- A value can only have one owner at a time. If you assign it to a new variable, the ownership moves.
These rules apply to all types that store data on the heap, like strings, vectors, or any user-defined type.
Ownership in Action
Here is a simple example:
fn main() {
let name = String::from("Alice");
let user = name;
println!("Hello, {}", name); // This line causes a compile-time error
}
Rust will stop you because ownership of name moves to user, and name is no longer valid. This avoids any accidental use of freed memory.
Only one variable can own a heap-allocated value at a time. When user takes the value, name becomes invalid.
Stack vs Heap
This behavior mostly affects heap-allocated values like String or Vec. If you use primitive types like integers, copying is allowed:
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y); // Both work fine
This works because i32 is stored on the stack and implements a special trait called Copy.
Cloning a Value
If you want to use both variables, you can clone the data explicitly:
let name = String::from("Alice");
let user = name.clone();
println!("name: {}, user: {}", name, user); // This works
Cloning performs a deep copy of the data on the heap.
Use this carefully, as cloning is more expensive than moving ownership.
Ownership and Functions
Passing a variable to a function also moves ownership:
fn greet(name: String) {
println!("Hello, {}", name);
}
fn main() {
let person = String::from("Alice");
greet(person);
println!("{}", person); // Error: person moved to greet
}
If you want to keep using the variable after the function call, you must return it or use a reference.
You can return ownership back like this:
fn give_back(name: String) -> String {
name
}
fn main() {
let name = String::from("Alice");
let name = give_back(name); // Ownership moves in, then out again
}
When Does Rust Drop a Value?
When a value’s owner goes out of scope, Rust automatically calls drop() to free the memory:
fn main() {
let name = String::from("Alice");
// drop() is called automatically here
}
No need for manual free() or a garbage collector. Rust does it for you, safely and efficiently.
Summary
Ownership is how Rust keeps memory safe without needing a garbage collector. Each value has a single owner, and when ownership is transferred (moved), the previous variable becomes invalid. This is the foundation for other key Rust features like borrowing and lifetimes, which we will cover next.