Understanding “Ownership” in Rust

Hey there, future coding wizard! 👋
You’ve heard of computer programs, right? Like the apps on your phone or the games you play. These programs need to store information in the computer’s memory. Imagine memory like a giant set of empty boxes. When your program needs to remember something, it puts that information into a box.
Now, here’s the tricky part: who owns which box? And when is it safe to throw a box away? If two parts of your program try to use the same box at the same time, or if you accidentally throw away a box that another part of your program still needs, things can go horribly wrong! Your program might crash, or worse without you knowing why.
This is where Rust comes in with its super cool and unique concept called Ownership.
In this article, I’ll break down ownership in a simple and beginner-friendly way. If you’re new to Rust or programming in general, don’t worry — this is for you.
What is ownership?
Ownership in Rust is a set of rules that governs how memory is managed during a program’s execution.
Think of it like this: Imagine you have a pen.
Rule 1: Every pen has only one owner. You (or anyone else) can own it.
Rule 2: Only one person can own the pen at a time. You can’t both fully own the same pen at the same time. If you give your pen to your friend, they now own it. You don’t.
Rule 3: When the owner is done using (or leaves the room), the pen goes back in its case. No mess left behind!
Memory management
To understand ownership, it’s crucial to understand how Rust manages memory. Rust uses two main memory regions:
- The Stack:
Stores values with known, fixed size
Values are pushed and popped in last-in, first-out order (LIFO)
Fast and efficient access
Automatic cleanup when values go out of scope
2. The Heap:
Stores values with unknown or dynamic size
Memory is allocated and deallocated manually
Slower access than stack, but more flexible
Requires careful management to prevent memory leaks
Rule 1: Every Piece of Data Has an Owner
When you create a piece of data, a variable immediately becomes its owner.
fn main() {
let greeting = String::from("Hello, world!");
println!("{}", greeting);
}
Here’s what’s happening:
let greeting = ...
creates a new piece of text and stores it in a variable calledgreeting
.greeting
owns that text (Hello, world!).When the program ends, Rust throws away that text automatically.
Rule 2: Only One Owner at a Time
What Happens When You Assign One Variable to Another?
Heap Data like String
, Vec
, and custom structs are stored on the heap. These are moved, not copied.
Let’s say you want to give a text to someone else:
fn main() {
let car1 = String::from("Red Sports Car"); // 'car1' owns "Red Sports Car"
println!("Car 1: {}", car1); // Works!
let car2 = car1; // Ownership of "Red Sports Car" MOVES from 'car1' to 'car2'!
// 'car1' no longer owns it!
// println!("Car 1 again: {}", car1); // <-- ERROR! You can't use 'car1' here
// because it no longer owns the car!
println!("Car 2: {}", car2); // Works! 'car2' now owns it.
}
oops! why run-time error?
Because when you do let car2= car1
, Rust moves the ownership of the text to car2
. Now car1
is no longer valid—it gave up ownership.
It’s like handing over your lunch to a friend. Once they have it, you can’t eat it too!
What about simple things?
Stack Data like integers, booleans, and characters are stored on the stack. These implement the Copy
trait, meaning they're copied rather than moved:
It’s like having a small sticker; you can just peel off a new one easily.
fn main() {
let num1 = 10; // 'num1' owns the number 10
let num2 = num1; // The number 10 is COPIED to 'num2'. Both 'num1' and 'num2' have 10.
println!("Num 1: {}, Num 2: {}", num1, num2); // Both work fine!
}
The difference is that a String
can be very large and complicated, so copying it would be slow. Rust chooses to move ownership to keep things fast and prevent mistakes. Numbers are small and simple, so copying is fast and easy.
How Can I Keep Using Both?
If you really need both variables to have the same value, you can clone it:
fn main() {
let greeting = String::from("Hello!");
let new_greeting = greeting.clone();
println!("{}", greeting); // ✅ This works
println!("{}", new_greeting); // ✅ So does this
}
Now both greeting
and new_greeting
have their own copies. But remember: cloning takes more computer work, so don’t use it too often.
What About Functions?
Let’s see how ownership works when we give values to functions.
fn main() {
let name = String::from("Rusty");
print_name(name);
// println!("{}", name); // ❌ This won’t work
}
fn print_name(n: String) {
println!("Hello, {}!", n);
}
Here, name
is moved into the function. After that, the main program no longer owns it.
How Can I Keep Ownership While Using a Function?
Instead of moving the value, you can borrow it using a reference:
fn main() {
let name = String::from("Rusty");
print_name(&name); // Give a reference
println!("{}", name); // ✅ This still works
}
fn print_name(n: &String) {
println!("Hello, {}!", n);
}
Using &
means you’re just borrowing the value, not giving it away.
You can think of it like letting a friend read your book without giving it to them forever.
Two types of borrow:
- Immutable Borrow: You let someone look at your bike, but they can’t change it.
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // Pass a reference
println!("The length of '{}' is {}.", s1, len); // s1 is still valid!
}
fn calculate_length(s: &String) -> usize { // Parameter is a reference
s.len()
} // s goes out of scope, but it doesn't own the data, so nothing is dropped
- Mutable Borrow: You let someone fix your bike, but only one person can do this at a time. References are immutable by default, but you can create mutable references:
fn main() {
let mut s = String::from("hello");
change(&mut s); // Pass a mutable reference
println!("{}", s); // Prints "hello, world"
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
However, Rust enforces a crucial restriction: you can have either one mutable reference OR any number of immutable references, but not both:
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
// let r2 = &mut s; // Error: cannot have two mutable references
println!("{}", r1);
}
Ownership in a Nutshell
Rust gives memory safety without a garbage collector.
Values have owners, and ownership can move.
When an owner goes out of scope, the value is dropped.
Use references to avoid moving ownership when you don’t need to.
Ownership might seem strict at first, but it saves you from a lot of bugs: memory leaks, dangling pointers, race conditions — gone. By mastering ownership, you’ll unlock the full potential of Rust, be able to build robust and efficient software and start exploring its powerful, safe way of handling data!.
If you found this helpful, give it a clap 👏 and share.
Thank you.
Happy Rusting! 🦀
Subscribe to my newsletter
Read articles from Jolade Okunlade directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
