Rust Ownership System
Master Rust's unique ownership system that provides memory safety without garbage collection through compile-time checks.
Ownership Rules
Three fundamental ownership rules:
-
Each value in Rust has a variable that's its owner
-
There can only be one owner at a time
-
When the owner goes out of scope, the value is dropped
fn ownership_basics() { // s owns the String let s = String::from("hello");
// Ownership moved to s2
let s2 = s;
// Error: s no longer owns the value
// println!("{}", s);
println!("{}", s2); // OK
} // s2 dropped here, memory freed
Move Semantics
Ownership transfer (move):
fn move_semantics() { let s1 = String::from("hello");
// Ownership moved to function
takes_ownership(s1);
// Error: s1 no longer valid
// println!("{}", s1);
}
fn takes_ownership(s: String) { println!("{}", s); } // s dropped here
// Return ownership from function fn gives_ownership() -> String { String::from("hello") }
fn main() { let s = gives_ownership(); println!("{}", s); }
Copy trait for stack types:
fn copy_types() { // Types implementing Copy are duplicated, not moved let x = 5; let y = x; // x copied to y
println!("x: {}, y: {}", x, y); // Both valid
// Copy types: integers, floats, bool, char, tuples of Copy types
let tuple = (1, 2.5, true);
let tuple2 = tuple;
println!("{:?} {:?}", tuple, tuple2); // Both valid
}
Borrowing
Immutable borrowing (references):
fn immutable_borrow() { let s1 = String::from("hello");
// Borrow s1 (immutable reference)
let len = calculate_length(&s1);
println!("Length of '{}' is {}", s1, len); // s1 still valid
}
fn calculate_length(s: &String) -> usize { s.len() } // s goes out of scope, but doesn't drop the value
// Multiple immutable borrows allowed fn multiple_immutable_borrows() { let s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &s;
println!("{}, {}, {}", r1, r2, r3); // OK
}
Mutable borrowing:
fn mutable_borrow() { let mut s = String::from("hello");
// Mutable borrow
change(&mut s);
println!("{}", s); // "hello, world"
}
fn change(s: &mut String) { s.push_str(", world"); }
// Only ONE mutable borrow allowed at a time fn mutable_borrow_rules() { let mut s = String::from("hello");
let r1 = &mut s;
// let r2 = &mut s; // Error: cannot borrow mutably twice
println!("{}", r1);
}
// Cannot mix mutable and immutable borrows fn no_mix_borrows() { let mut s = String::from("hello");
let r1 = &s; // Immutable borrow
let r2 = &s; // Another immutable borrow
// let r3 = &mut s; // Error: cannot borrow mutably while immutably borrowed
println!("{} {}", r1, r2);
}
Non-lexical lifetimes (NLL):
fn non_lexical_lifetimes() { let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} {}", r1, r2);
// r1 and r2 no longer used after this point
// OK: immutable borrows ended
let r3 = &mut s;
println!("{}", r3);
}
Lifetimes
Lifetime annotations:
// Lifetime 'a ensures returned reference lives as long as both inputs fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
fn main() { let string1 = String::from("long string"); let string2 = String::from("short");
let result = longest(&string1, &string2);
println!("Longest: {}", result);
}
Lifetime in structs:
// Struct holds a reference, needs lifetime annotation struct ImportantExcerpt<'a> { part: &'a str, }
impl<'a> ImportantExcerpt<'a> { fn level(&self) -> i32 { 3 }
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention: {}", announcement);
self.part
}
}
fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first_sentence = novel.split('.').next().unwrap();
let excerpt = ImportantExcerpt {
part: first_sentence,
};
println!("{}", excerpt.part);
}
Lifetime elision rules:
// Compiler infers lifetimes in these cases:
// Rule 1: Each reference parameter gets its own lifetime fn first_word(s: &str) -> &str { // Expanded: fn first_word<'a>(s: &'a str) -> &'a str s.split_whitespace().next().unwrap_or("") }
// Rule 2: If one input lifetime, assign to all outputs fn foo(s: &str) -> &str { s }
// Rule 3: If &self or &mut self, its lifetime assigned to outputs impl<'a> ImportantExcerpt<'a> { fn get_part(&self) -> &str { // Expanded: fn get_part<'a>(&'a self) -> &'a str self.part } }
Static lifetime:
// 'static means reference lives for entire program duration fn static_lifetime() -> &'static str { "This string is stored in binary" }
// String literals have 'static lifetime let s: &'static str = "hello world";
Smart Pointers
Box for heap allocation:
fn box_pointer() { // Allocate value on heap let b = Box::new(5); println!("b = {}", b); } // b deallocated when out of scope
// Recursive types require Box enum List { Cons(i32, Box<List>), Nil, }
use List::{Cons, Nil};
fn recursive_type() { let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); }
Rc for reference counting:
use std::rc::Rc;
fn rc_example() { let a = Rc::new(5);
// Clone Rc pointer, increment count
let b = Rc::clone(&a);
let c = Rc::clone(&a);
println!("Reference count: {}", Rc::strong_count(&a)); // 3
// All owners must go out of scope before value is dropped
}
// Sharing data in graph structures enum RcList { Cons(i32, Rc<RcList>), Nil, }
use RcList::{Cons as RcCons, Nil as RcNil};
fn shared_ownership() { let a = Rc::new(RcCons(5, Rc::new(RcCons(10, Rc::new(RcNil)))));
// b and c both reference a
let b = RcCons(3, Rc::clone(&a));
let c = RcCons(4, Rc::clone(&a));
}
RefCell for interior mutability:
use std::cell::RefCell;
fn refcell_example() { let value = RefCell::new(5);
// Borrow mutably
*value.borrow_mut() += 1;
// Borrow immutably
println!("Value: {}", value.borrow());
}
// Combine Rc and RefCell for shared mutable data use std::rc::Rc; use std::cell::RefCell;
fn rc_refcell() { let value = Rc::new(RefCell::new(5));
let a = Rc::clone(&value);
let b = Rc::clone(&value);
*a.borrow_mut() += 10;
*b.borrow_mut() += 20;
println!("Value: {}", value.borrow()); // 35
}
Ownership Patterns
Taking ownership vs borrowing:
// Take ownership when you need to consume the value fn consume(s: String) { println!("{}", s); }
// Borrow when you only need to read fn read(s: &String) { println!("{}", s); }
// Borrow mutably when you need to modify fn modify(s: &mut String) { s.push_str(" modified"); }
fn main() { let mut s = String::from("hello");
read(&s); // Still own s
modify(&mut s); // Still own s
consume(s); // No longer own s
}
Builder pattern with ownership:
struct Config { name: String, value: i32, }
struct ConfigBuilder { name: Option<String>, value: Option<i32>, }
impl ConfigBuilder { fn new() -> Self { ConfigBuilder { name: None, value: None, } }
// Take ownership and return ownership
fn name(mut self, name: String) -> Self {
self.name = Some(name);
self
}
fn value(mut self, value: i32) -> Self {
self.value = Some(value);
self
}
fn build(self) -> Config {
Config {
name: self.name.unwrap_or_default(),
value: self.value.unwrap_or(0),
}
}
}
fn main() { let config = ConfigBuilder::new() .name(String::from("app")) .value(42) .build(); }
Slice Types
String slices:
fn string_slices() { let s = String::from("hello world");
// Slice references part of string
let hello = &s[0..5];
let world = &s[6..11];
// Shorthand
let hello = &s[..5];
let world = &s[6..];
let whole = &s[..];
println!("{} {}", hello, world);
}
fn first_word(s: &str) -> &str { let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[..i];
}
}
&s[..]
}
Array slices:
fn array_slices() { let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // &[i32]
assert_eq!(slice, &[2, 3]);
}
Clone vs Copy
Understanding Clone trait:
#[derive(Clone)] struct Point { x: f64, y: f64, }
fn clone_example() { let p1 = Point { x: 1.0, y: 2.0 };
// Explicit clone (deep copy)
let p2 = p1.clone();
// Both valid
println!("{} {}", p1.x, p2.x);
}
Copy trait limitations:
// Copy requires all fields to implement Copy #[derive(Copy, Clone)] struct Coord { x: i32, y: i32, }
// Cannot derive Copy with String field // #[derive(Copy, Clone)] // Error struct Person { name: String, // String doesn't implement Copy }
Drop Trait
Custom cleanup with Drop:
struct CustomSmartPointer { data: String, }
impl Drop for CustomSmartPointer { fn drop(&mut self) { println!("Dropping CustomSmartPointer with data: {}", self.data); } }
fn main() { let c = CustomSmartPointer { data: String::from("my stuff"), };
let d = CustomSmartPointer {
data: String::from("other stuff"),
};
println!("CustomSmartPointers created");
} // d dropped, then c dropped
Manual drop:
fn manual_drop() { let c = CustomSmartPointer { data: String::from("some data"), };
println!("Before drop");
drop(c); // Manually drop early
println!("After drop");
}
When to Use This Skill
Use rust-ownership-system when you need to:
-
Understand Rust's memory management model
-
Write memory-safe code without garbage collection
-
Handle ownership transfer between functions
-
Work with references and borrowing
-
Implement structs with lifetime parameters
-
Use smart pointers (Box, Rc, RefCell)
-
Debug borrow checker errors
-
Choose between ownership, borrowing, and cloning
-
Implement custom Drop behavior
-
Work with slices and references safely
Best Practices
-
Prefer borrowing over ownership transfer when possible
-
Use immutable borrows by default, mutable only when needed
-
Keep borrow scopes as small as possible
-
Use lifetime elision when compiler can infer lifetimes
-
Choose appropriate smart pointer for use case
-
Avoid RefCell in performance-critical code
-
Use slices instead of owned types in function signatures
-
Clone only when necessary (it's explicit and visible)
-
Implement Drop for custom cleanup logic
-
Let compiler guide you with borrow checker errors
Common Pitfalls
-
Moving value and trying to use it afterward
-
Creating multiple mutable borrows simultaneously
-
Mixing mutable and immutable borrows
-
Returning references to local variables
-
Fighting the borrow checker instead of understanding it
-
Overusing clone() to avoid ownership issues
-
Not understanding lifetime relationships
-
Circular references with Rc (use Weak)
-
Panicking with RefCell borrow violations at runtime
-
Using 'static lifetime incorrectly
Resources
-
Rust Book - Ownership
-
Rust Book - Lifetimes
-
Rust By Example - Scopes
-
The Rustonomicon
-
Too Many Linked Lists