Rust Ownership â The Complete Guide
Rust ownership is a set of rules that governs how memory is managed, ensuring every value has exactly one owner and is automatically freed when that owner goes out of scope.
What You'll Learn
In this tutorial, you'll learn the three rules of Rust ownership, how moves and copies differ, how functions affect ownership, and why ownership eliminates memory bugs at compile time.
Why It Matters
Ownership is Rust's most distinctive feature. It replaces garbage collection and manual memory management with a compile-time system that guarantees no dangling pointers, no double frees, and no memory leaks. This is critical for systems where reliability is paramount â like the Linux kernel, web browsers, and antivirus engines.
Real-World Use
Dropbox's file sync engine uses Rust ownership to manage file handles without leaks. The Tokio async runtime uses ownership to ensure tasks release their resources. Durga Antivirus Pro's signature scanner uses ownership to guarantee every opened file handle is closed, even during early return on error.
flowchart TD
A[Value Created] --> B{Owner Exists?}
B -->|Yes| C[Owner controls value]
C --> D{Value Moved?}
D -->|Yes| E[New owner acquires control]
D -->|No| F{Owner goes out of scope?}
E --> G[Old owner invalidated]
G --> F
F -->|Yes| H[Value dropped / memory freed]
F -->|No| C
Prerequisites: Rust basics and memory management concepts. Understanding stack vs heap helps.
The Three Ownership Rules
Rule 1: Each value has exactly one owner
The owner is the variable that holds the value. There is never more than one owner at a time.
fn main() {
let owner = String::from("I own this string");
println!("Owner says: {}", owner);
} // owner goes out of scope, string is freed
Expected output:
Owner says: I own this string
Rule 2: When the owner goes out of scope, the value is dropped
Rust automatically calls drop() on the value when its owner exits scope. No explicit free() needed.
struct Token {
id: u32,
}
impl Drop for Token {
fn drop(&mut self) {
println!("Token {} is being freed", self.id);
}
}
fn main() {
let t = Token { id: 42 };
println!("Token {} is alive", t.id);
// t goes out of scope here, drop() runs automatically
}
Expected output:
Token 42 is alive
Token 42 is being freed
Rule 3: Ownership can be transferred (moved)
Assigning a value to another variable moves ownership. The original variable is no longer valid.
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 is MOVED to s2
// println!("{}", s1); // ERROR: borrow of moved value
println!("s2 owns it now: {}", s2);
}
fn process_string(s: String) {
println!("Processing: {}", s);
} // s is dropped here
fn main2() {
let name = String::from("Alice");
process_string(name); // ownership moved into function
// println!("{}", name); // ERROR: value used after move
}
Expected output:
s2 owns it now: hello
Processing: Alice
Copy vs Move
Simple types that implement the Copy trait are copied instead of moved. These include integers, floats, booleans, and char.
fn main() {
// Copy types (implement Copy trait)
let x = 42;
let y = x; // x is COPIED, not moved
println!("Both x ({}) and y ({}) are valid", x, y);
// Move types (do not implement Copy)
let s1 = String::from("hello");
let s2 = s1; // s1 is MOVED
println!("s2 = {}", s2);
// println!("s1 = {}", s1); // ERROR
// Tuples of Copy types are also Copy
let pair = (1, true);
let pair2 = pair; // copied
println!("Both pair ({:?}) and pair2 ({:?}) valid", pair, pair2);
}
Expected output:
Both x (42) and y (42) are valid
s2 = hello
Both pair ((1, true)) and pair2 ((1, true)) valid
Functions and Ownership
Passing a value to a function transfers ownership. Returning a value transfers ownership back.
fn take_and_give(s: String) -> String {
println!("Got: {}", s);
let new_s = format!("{} World", s);
new_s // ownership returned to caller
}
fn main() {
let s1 = String::from("Hello");
let s2 = take_and_give(s1); // ownership transferred, then returned
println!("Back in main: {}", s2);
// s2 is dropped here
}
Expected output:
Got: Hello
Back in main: Hello World
Ownership and Security Implications
Rust's ownership model directly prevents security vulnerabilities common in C and C++:
- No buffer overflows: Out-of-bounds access is caught at runtime or compile time
- No use-after-free: The compiler prevents accessing freed memory
- No double free: Each value has exactly one owner who drops it once
- No memory leaks: All resources are freed when their owner scopes end
This makes Rust the preferred language for security-critical systems. Durga Antivirus Pro's on-access scanner processes hundreds of files per second with zero memory safety bugs precisely because of Rust's ownership guarantees.
Common Mistakes
1. Using a Value After Move
Once ownership is moved, the original variable is invalid. Accessing it causes a compile error. Solution: use references instead of moving, or clone the value if you need both copies.
2. Confusing Copy and Move
Primitive types are copied automatically. Heap-allocated types like String and Vec are moved. Know which types implement Copy.
3. Not Returning Ownership from Functions
If a function takes ownership and you need it back, return it. Alternatively, use references for borrowed access.
4. Unnecessary Cloning
Overusing .clone() to avoid ownership issues defeats Rust's performance. Prefer references and borrowing instead.
5. Ignoring the Drop Implementation
Not implementing Drop for types that manage system resources (files, sockets, locks) can cause resource leaks. Always implement Drop for resource-holding types.
Practice Questions
1. What are the three rules of Rust ownership? Each value has one owner. When the owner goes out of scope, the value is dropped. Ownership can be transferred (moved) to another owner.
2. What is the difference between Copy and Move? Copy types (integers, floats, bools) are duplicated on assignment. Move types (String, Vec, Box) transfer ownership, invalidating the original variable.
3. What happens to a value when it is passed to a function? Ownership transfers to the function parameter. The original variable can no longer access the value unless the function returns ownership.
4. How does ownership improve security? It prevents use-after-free, double free, memory leaks, and buffer overflows at compile time. These bugs account for most critical CVEs in C/C++ programs.
5. Challenge: Write a function that takes ownership of a Vec<i32>, doubles each element, and returns the vector. Verify the original variable is no longer valid.
Mini Project: Safe File Reader with Ownership
use std::fs::File;
use std::io::{self, Read};
struct SafeFile {
contents: String,
}
impl SafeFile {
fn open(path: &str) -> io::Result<SafeFile> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
println!("File loaded, {} bytes read", contents.len());
Ok(SafeFile { contents })
}
fn take_contents(self) -> String {
self.contents // ownership moves to caller
}
}
fn main() -> io::Result<()> {
let f = SafeFile::open("Cargo.toml")?;
let data = f.take_contents(); // ownership transferred
// println!("{}", f.contents); // ERROR: f is consumed
println!("File starts with: {}", &data[..data.len().min(80)]);
Ok(())
}
FAQ
Related Concepts
What's Next
Master Borrowing & References to learn how to access data without taking ownership, then study Lifetimes for advanced reference safety.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro