# Rust Ownership and Borrowing: The Guide That Demystifies Everything > Master Rust ownership and borrowing with practical examples. Understand move semantics, references, lifetimes, and the borrow checker to write safe, efficient Rust code. - Published: 2026-03-31 - Updated: 2026-03-31 - Author: Anthony Fillion-Maillet - Tags: rust, ownership, borrowing, memory-management, systems-programming - Reading time: 10 min --- Rust ownership and borrowing form the foundation of Rust's memory safety guarantees. Unlike garbage-collected languages, Rust enforces strict rules at compile time through the borrow checker, eliminating entire classes of bugs -- null pointer dereferences, data races, and use-after-free errors -- without runtime overhead. > **The Three Rules of Ownership** > > Every value in Rust has exactly one owner. When the owner goes out of scope, the value is dropped. Ownership can be transferred (moved) or temporarily lent (borrowed). These three rules replace garbage collection entirely. ## How Move Semantics Replace Garbage Collection Most languages let multiple variables point to the same heap-allocated data. Rust takes a different approach: assigning a heap value to another variable *moves* it, invalidating the original binding. The compiler enforces this at zero cost. ```rust // move_semantics.rs fn main() { let original = String::from("interview prep"); let moved = original; // ownership transfers here // println!("{}", original); // compile error: value moved println!("{}", moved); // works fine } ``` This prevents double-free bugs. The `String` type allocates on the heap, so Rust ensures only one variable owns that allocation at any time. Stack-only types like `i32` or `bool` implement the `Copy` trait and are duplicated instead of moved. ```rust // copy_vs_move.rs fn main() { let x: i32 = 42; let y = x; // copy, not move -- i32 is Copy println!("x = {}, y = {}", x, y); // both valid let s1 = String::from("hello"); let s2 = s1.clone(); // explicit deep copy println!("s1 = {}, s2 = {}", s1, s2); // both valid after clone } ``` The distinction between `Copy` and `Clone` matters in interviews: `Copy` is implicit and cheap (bitwise), while `Clone` is explicit and can be expensive (heap allocation). ## Borrowing with Immutable References Transferring ownership everywhere would make code impractical. Rust borrowing solves this by lending access to a value without transferring ownership. An immutable reference (`&T`) allows read-only access, and multiple immutable references can coexist. ```rust // immutable_borrowing.rs fn calculate_length(s: &String) -> usize { s.len() // read access through the reference } // s goes out of scope, but doesn't drop the String (not the owner) fn main() { let greeting = String::from("hello, Rust"); let len = calculate_length(&greeting); // borrow, don't move println!("'{}' has {} characters", greeting, len); // greeting still valid } ``` The `&` symbol creates a reference that borrows the value. The function signature `&String` declares that `calculate_length` borrows without taking ownership. After the function returns, the caller retains full ownership. > **Borrowing Rules at a Glance** > > At any given time, a value can have either: many immutable references (`&T`), OR exactly one mutable reference (`&mut T`). Never both simultaneously. This rule prevents data races at compile time. ## Mutable References and the Exclusivity Rule Mutable references (`&mut T`) grant write access but enforce exclusivity: only one mutable reference to a value can exist in a given scope. This prevents two pieces of code from modifying the same data simultaneously. ```rust // mutable_borrowing.rs fn append_greeting(s: &mut String) { s.push_str(", welcome to Rust!"); // modify through mutable ref } fn main() { let mut message = String::from("Hello"); append_greeting(&mut message); println!("{}", message); // "Hello, welcome to Rust!" } ``` The `mut` keyword appears in three places: the variable binding (`let mut`), the reference type (`&mut`), and the function parameter. All three are required. Attempting to create a second mutable reference in the same scope triggers a compile error. ```rust // exclusivity_rule.rs fn main() { let mut data = String::from("shared state"); let r1 = &mut data; // let r2 = &mut data; // compile error: second mutable borrow println!("{}", r1); // After r1's last usage, a new mutable borrow is allowed let r3 = &mut data; // this works -- non-lexical lifetimes r3.push_str(" updated"); println!("{}", r3); } ``` Rust 2021 edition uses non-lexical lifetimes (NLL): a borrow ends at its last point of use, not at the end of the scope block. This makes the exclusivity rule more ergonomic without sacrificing safety. ## Lifetimes: Telling the Compiler How Long References Live Lifetimes are Rust's way of ensuring references never outlive the data they point to. Most of the time, the compiler infers lifetimes automatically through *lifetime elision rules*. Explicit annotations become necessary when multiple references interact. ```rust // lifetime_annotation.rs // 'a declares: the returned reference lives as long as // the shortest-lived input reference fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } fn main() { let result; let string1 = String::from("Rust ownership"); { let string2 = String::from("borrowing"); result = longest(string1.as_str(), string2.as_str()); println!("Longest: {}", result); // valid: both strings alive } // println!("{}", result); // would fail: string2 dropped } ``` The `'a` syntax is a lifetime parameter, not a new concept -- it annotates relationships that already exist. The function signature says: "the output reference cannot outlive either input reference." The compiler uses this to prevent dangling references. ## Struct Borrowing and Lifetime Bounds Structs that hold references must declare lifetime parameters. This ensures the struct cannot outlive the data it references -- a common source of dangling pointers in C/C++. ```rust // struct_lifetime.rs struct Excerpt<'a> { text: &'a str, // this struct borrows a string slice } impl<'a> Excerpt<'a> { fn summary(&self) -> &str { let end = self.text.len().min(20); &self.text[..end] // return a slice of the borrowed text } } fn main() { let article = String::from("Rust ownership model eliminates memory bugs"); let excerpt = Excerpt { text: article.as_str(), }; println!("Summary: {}", excerpt.summary()); } ``` The lifetime `'a` in `Excerpt<'a>` ties the struct's validity to the underlying string. Dropping `article` before `excerpt` would trigger a compile error. > **Common Interview Pitfall** > > Dangling reference questions appear frequently in [Rust interviews](/technologies/rust/interview-questions/smart-pointers). The answer is always: Rust prevents them at compile time through lifetime analysis. No runtime checks, no null pointers. ## Ownership Patterns in Real-World Rust Production Rust code relies on a few recurring ownership patterns. Recognizing them accelerates both development and interview performance. ```rust // ownership_patterns.rs // Pattern 1: Take ownership, return a new value fn process_and_return(mut input: String) -> String { input.push_str(" -- processed"); input // ownership transfers to caller } // Pattern 2: Borrow for read-only inspection fn contains_keyword(text: &str, keyword: &str) -> bool { text.to_lowercase().contains(&keyword.to_lowercase()) } // Pattern 3: Borrow mutably for in-place modification fn sanitize(input: &mut String) { *input = input.trim().to_string(); } fn main() { // Pattern 1 let raw = String::from("user input"); let processed = process_and_return(raw); // raw is now invalid, processed owns the data // Pattern 2 let found = contains_keyword(&processed, "input"); println!("Contains 'input': {}", found); // Pattern 3 let mut padded = String::from(" spaces everywhere "); sanitize(&mut padded); println!("Sanitized: '{}'", padded); } ``` Choosing between these patterns follows a simple heuristic: borrow immutably by default, borrow mutably when modification is needed, and transfer ownership only when the caller no longer needs the value. This approach is covered in depth in the [Rust fundamentals guide](/blog/rust/rust-basics-experienced-developers). ## Borrow Checker Errors and How to Fix Them The borrow checker produces specific error codes. Understanding the most common ones turns frustrating compile errors into straightforward fixes. ```rust // common_fixes.rs fn main() { // Error E0502: cannot borrow as mutable because also borrowed as immutable let mut scores = vec![90, 85, 78]; // Fix: finish using the immutable borrow before mutating let first = scores[0]; // copy (i32 is Copy), no active borrow scores.push(95); // mutable borrow -- no conflict println!("First: {}, All: {:?}", first, scores); // Error E0382: use of moved value let name = String::from("Alice"); let greeting = format!("Hello, {}", name); // format! borrows, doesn't move println!("{} says {}", name, greeting); // both valid // Error E0597: borrowed value does not live long enough let outer; { let inner = String::from("temporary"); outer = inner; // move instead of borrow -- extends lifetime } println!("{}", outer); // works: outer owns the value } ``` Each fix follows the same principle: restructure the code so that borrows and ownership align with Rust's rules. Fighting the borrow checker usually signals a design issue that would cause bugs in other languages. For more advanced patterns involving [concurrency and borrowing](/technologies/rust/interview-questions/concurrency-basics), shared ownership types like `Arc` and `Mutex` become essential. ## Conclusion - Every Rust value has one owner; ownership transfers on assignment (move semantics) unless the type implements `Copy` - Immutable references (`&T`) allow shared read access; mutable references (`&mut T`) enforce exclusive write access - The borrow checker prevents data races and dangling references at compile time with zero runtime cost - Lifetimes annotate reference relationships -- they describe existing constraints, not new ones - When the borrow checker rejects code, restructure the ownership flow rather than reaching for `unsafe` - Practice these patterns with the [Rust interview questions](/blog/rust/rust-interview-questions) to build fluency before technical interviews --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/rust/rust-ownership-borrowing-demystified