Ownership is Rust's core memory management model — every value has exactly one owner, and when the owner goes out of scope, the value is automatically dropped (freed). No garbage collector. No manual free. Enforced entirely at compile time.
Borrowing lets you use a value without taking ownership.
You can have either any number of immutable references
(&T)
or exactly one mutable reference
(&mut T)
— never both at the same time. The borrow checker enforces this statically.
Single Owner
Each value in Rust has exactly one variable that owns it at any given time.
Move on Assignment
When ownership is assigned to another variable, the original variable is invalidated — this is a move. Types that implement Copy are the exception.
Drop on Scope Exit
When the owner goes out of scope, Rust automatically calls drop(), freeing heap memory. Zero runtime cost.
// String is heap-allocated → ownership MOVES, not copies fn main() { let s1 = String::from("hello"); let s2 = s1; // s1 is MOVED into s2 // println!("{}", s1); // ❌ ERROR: value borrowed after move println!("{}", s2); // ✅ s2 is the owner now }
// Scalar types (i32, bool, f64, char) implement Copy — stack-only fn main() { let x: i32 = 42; let y = x; // COPY — x is still valid println!("{} {}", x, y); // ✅ both valid }
// .clone() deep-copies heap data — explicit, intentional cost fn main() { let s1 = String::from("world"); let s2 = s1.clone(); // deep copy of heap data println!("{} {}", s1, s2); // ✅ both valid, both own separate memory }
// Immutable borrow — many readers, no mutation fn print_len(s: &String) { // borrows, does NOT take ownership println!("len = {}", s.len()); } // Mutable borrow — exclusive writer fn push_str(s: &mut String) { s.push_str(", world"); } fn main() { let mut s = String::from("hello"); print_len(&s); // immutable borrow push_str(&mut s); // mutable borrow println!("{}", s); // ✅ "hello, world" }
fn main() { let mut s = String::from("hello"); let r1 = &s; // immutable borrow ✅ let r2 = &s; // another immutable borrow ✅ let r3 = &mut s; // ❌ cannot borrow as mutable — r1, r2 still active println!("{} {} {}", r1, r2, r3); }
Zero Memory Bugs at Runtime
No dangling pointers, no use-after-free, no double-free. The borrow checker catches them all at compile time — your production binary ships without a class of bugs.
No GC Pauses
No garbage collector means predictable, low-latency performance. Critical for real-time systems, game engines, trading systems, and high-throughput web servers.
Thread Safety Guaranteed
Ownership rules extend to concurrency. You cannot accidentally share mutable state across threads — the compiler prevents data races entirely.
Explicit Resource Management
RAII via Drop: file handles, DB connections, mutex locks — all released deterministically when the owner goes out of scope. No leaks by design.
String, Vec.Copy: implicitly duplicates stack-only data. The original stays valid. Types like
i32, bool, f64 implement Copy.Clone: explicit, deep copy of heap data via
.clone(). Both variables own independent data. Use it consciously — it has a runtime cost.
&mut T) at any point in time, and zero immutable references when a mutable reference is active. This prevents data races at compile time — if multiple references could mutate simultaneously, you'd have undefined behavior. Rust makes the concurrency contract explicit and statically verified.
&str vs String?
+
&str (a string slice / borrowed view) for read-only access to string data — function parameters, string literals, slices of a String. It's zero-copy and cheap.Use
String when you need to own the string data, grow it, or return it from a function. The general rule: prefer &str in function signatures for flexibility; use String when you need ownership or mutation.
Drop trait do, and how does it relate to ownership?
+
Drop trait defines custom cleanup logic that runs automatically when an owner goes out of scope — like a destructor. Rust calls drop() in reverse order of creation. This is RAII (Resource Acquisition Is Initialization): resources (file handles, locks, heap memory) are deterministically released when the owning struct is dropped. No GC, no explicit free needed.
| Pattern | What It Solves | Used In |
|---|---|---|
| Rc<T> / Arc<T> | Multiple ownership via reference counting. Arc is thread-safe. Use when you genuinely need shared ownership. |
Graph structures, shared caches, async runtimes |
| Cell<T> / RefCell<T> | Interior mutability — mutate data through an immutable reference. RefCell moves borrow checking to runtime. |
UI trees, mock objects, single-threaded shared state |
| Cow<'a, B> | Clone-on-Write — borrow data until mutation is needed, then clone. Avoids unnecessary allocations. | Parsers, serializers, string processing in hot paths |
| Pin<T> | Prevents a value from being moved in memory. Required for self-referential structs and async/await state machines. |
async futures, tokio tasks, custom allocators |
| ManuallyDrop<T> | Opt out of automatic drop(). Gives you manual control over destruction order. Used in unsafe code. |
FFI, custom allocators, arena memory management |
use std::cell::RefCell; use std::rc::Rc; fn main() { // Multiple owners + interior mutability without unsafe let shared = Rc::new(RefCell::new(vec![1, 2, 3])); let clone_a = Rc::clone(&shared); let clone_b = Rc::clone(&shared); clone_a.borrow_mut().push(4); // runtime borrow check clone_b.borrow_mut().push(5); println!("{:?}", shared.borrow()); // [1, 2, 3, 4, 5] // ⚠️ panics at runtime if you violate borrow rules // Use Arc> instead for multi-threaded scenarios }