These are not competing features. They are complementary tools with distinct trade-offs. This article unpacks both — semantics, performance, ergonomics, and the decisive question: which should you reach for?
But some data genuinely needs to be shared: a node in a graph owned by multiple edges, a configuration object threaded through many subsystems, a callback holding a reference into surrounding state. Enter reference counting — Rc<T> and Arc<T> — which shift ownership logic from compile time into a small runtime counter.
Every value in a Rust program has a single owner at any given time. When that owner goes out of scope, the value is dropped — memory freed, deterministically, at a known point in execution. No GC pause, no dangling pointer, no double-free. The compiler enforces all of this statically.
Rust's signature achievement is memory safety without a garbage collector. It accomplishes this through a disciplined ownership system — but that system deliberately has an escape hatch: reference counting.
O&B Ownership & Borrowing
The ownership model is Rust's core innovation. Every heap allocation has exactly one owner — the variable binding that "holds" it. Ownership can be moved to another binding, at which point the original is invalidated. It can never be silently copied (unless the type implements Copy ).
Move Semantics
ownership_move.rs fn () { let s1 = String :: ( "hello" ); let s2 = s1 ; // ownership MOVED — s1 is now invalid // println!("{}", s1); ← compile error: value borrowed after move println! ( "{}" , s2 ); // fine — s2 is the sole owner } // s2 dropped here, memory freed automatically
The borrow checker enforces the ownership rules. When s1 is assigned to s2 , the compiler understands that s1 can no longer be used — it has given up ownership. This eliminates use-after-free at zero runtime cost.
Borrowing — Shared (&T) and Mutable (&mut T)
... continue reading