Complete Guide to Rust Borrowing and References

Introduction to Borrowing and References

Borrowing is one of Rust's most distinctive and powerful features. It allows you to access data without taking ownership, enabling multiple parts of your code to work with the same data safely. The borrowing system, enforced by the compiler, prevents data races and dangling references at compile time.

Key Concepts

  • References: Pointers that borrow values without owning them
  • Borrowing: The act of creating references to data
  • Lifetimes: How long references are valid
  • Borrowing Rules: At any time, you can have either:
  • One mutable reference
  • Any number of immutable references
  • No Dangling References: The compiler ensures references always point to valid data

1. Basic References

Immutable References

fn main() { let s = String::from("hello"); // Create an immutable reference (borrow) let r1 = &s; let r2 = &s; // Multiple immutable references are allowed println!("r1: {}, r2: {}", r1, r2); // References are dereferenced automatically in many contexts let len = r1.len(); // No need for (*r1).len() println!("Length: {}", len); // Function that takes a reference print_string(&s); // s is still usable here println!("Original: {}", s); } fn print_string(s: &String) { println!("Borrowed: {}", s); }

Mutable References

fn main() { let mut s = String::from("hello"); // Create a mutable reference let r = &mut s; // Modify through the mutable reference r.push_str(", world"); println!("r: {}", r); // Can't use s while r is active // println!("{}", s); // Error: cannot borrow as immutable // After r goes out of scope, s can be used again println!("s: {}", s); // Now OK // Multiple mutable references not allowed let mut t = String::from("test"); let r1 = &mut t; // let r2 = &mut t; // Error: cannot borrow as mutable more than once // But we can create new mutable reference after previous is done let r1 = &mut t; r1.push_str("1"); // r1 goes out of scope here let r2 = &mut t; // This is fine r2.push_str("2"); println!("t: {}", t); }

Reference Rules Demonstration

fn main() { let mut data = String::from("data"); // Rule 1: Many immutable references allowed let immut1 = &data; let immut2 = &data; println!("immut1: {}, immut2: {}", immut1, immut2); // immut1 and immut2 go out of scope here // Now we can have a mutable reference let mut_ref = &mut data; mut_ref.push_str(" modified"); println!("mut_ref: {}", mut_ref); // mut_ref goes out of scope // Can't mix mutable and immutable let immut = &data; // let mut_ref = &mut data; // Error: cannot borrow as mutable println!("immut: {}", immut); // Scope matters: references are valid until last use let mut s = String::from("hello"); let r1 = &s; let r2 = &s; println!("r1: {}, r2: {}", r1, r2); // r1 and r2 no longer used after this line let r3 = &mut s; // This is allowed r3.push_str(" world"); println!("r3: {}", r3); }

2. Borrowing in Functions

Function Parameters with References

// Function that borrows immutably fn calculate_length(s: &String) -> usize { s.len() } // s goes out of scope, but since it was borrowed, nothing happens // Function that borrows mutably fn modify_string(s: &mut String) { s.push_str(" - modified"); } // Function that borrows multiple parameters fn compare_strings(s1: &String, s2: &String) -> bool { s1 == s2 } // Function returning a reference (requires lifetime annotation) fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] } fn main() { let mut s1 = String::from("hello"); let s2 = String::from("world"); // Immutable borrow let len = calculate_length(&s1); println!("Length of '{}': {}", s1, len); // Mutable borrow modify_string(&mut s1); println!("Modified: {}", s1); // Multiple borrows let are_equal = compare_strings(&s1, &s2); println!("Equal: {}", are_equal); // Returning reference let sentence = String::from("hello world"); let word = first_word(&sentence); println!("First word: {}", word); }

Borrowing and Ownership Transfer

fn take_ownership(s: String) { println!("Took ownership of: {}", s); } // s dropped here fn borrow(s: &String) { println!("Borrowed: {}", s); } fn borrow_and_modify(s: &mut String) { s.push_str("!"); } fn give_ownership() -> String { let s = String::from("new string"); s // Ownership transferred out } fn main() { let s1 = String::from("hello"); // Borrowing doesn't transfer ownership borrow(&s1); println!("Still have s1: {}", s1); // After taking ownership, can't use take_ownership(s1); // println!("{}", s1); // Error: s1 moved // Get ownership back let s2 = give_ownership(); println!("Got: {}", s2); // Mutable borrow let mut s3 = String::from("rust"); borrow_and_modify(&mut s3); println!("After modify: {}", s3); }

3. Dereferencing

Explicit Dereferencing

fn main() { let x = 5; let y = &x; // Dereference to get the value assert_eq!(5, x); assert_eq!(5, *y); // With mutable references let mut z = 10; let w = &mut z; *w += 5; // Modify through dereference println!("z after modification: {}", z); // Dereferencing complex types let s = String::from("hello"); let r = &s; // These are equivalent: println!("Length: {}", (*r).len()); println!("Length: {}", r.len()); // Auto-dereferencing // Dereferencing in patterns let maybe_ref = Some(&5); match maybe_ref { Some(&value) => println!("Got value: {}", value), None => (), } // Equivalent using deref in pattern match maybe_ref { Some(value) => println!("Got ref to: {}", value), None => (), } }

Auto-dereferencing Rules

struct Point { x: i32, y: i32, } impl Point { fn distance_from_origin(&self) -> f64 { ((self.x.pow(2) + self.y.pow(2)) as f64).sqrt() } } fn main() { let p = Point { x: 3, y: 4 }; let r = &p; let rr = &&p; let rrr = &&&p; // All work due to auto-dereferencing println!("r distance: {}", r.distance_from_origin()); println!("rr distance: {}", rr.distance_from_origin()); println!("rrr distance: {}", rrr.distance_from_origin()); // With mutable references let mut v = vec![1, 2, 3]; let mut_ref = &mut v; // push automatically dereferences mut_ref.push(4); println!("mut_ref: {:?}", mut_ref); // Indexing also auto-derefs println!("First element: {}", mut_ref[0]); // Method calls auto-deref to find the right implementation let s = String::from("hello"); let r1 = &s; let r2 = &r1; let r3 = &r2; // All work - Rust automatically dereferences as needed println!("r1 len: {}", r1.len()); println!("r2 len: {}", r2.len()); println!("r3 len: {}", r3.len()); }

4. Reference Types and Coercions

String References and Slices

fn main() { // &String vs &str let s = String::from("hello"); let string_ref: &String = &s; let str_slice: &str = &s[..]; // Explicit conversion // &String coerces to &str automatically (deref coercion) fn takes_str(s: &str) { println!("Got: {}", s); } takes_str(&s); // &String coerced to &str takes_str("literal"); // Works directly with &str // Array references and slices let arr = [1, 2, 3, 4, 5]; let array_ref: &[i32; 5] = &arr; let slice: &[i32] = &arr[1..4]; println!("Array ref: {:?}", array_ref); println!("Slice: {:?}", slice); // Vector references let v = vec![1, 2, 3]; let vec_ref: &Vec<i32> = &v; let vec_slice: &[i32] = &v[..]; println!("Vec ref: {:?}", vec_ref); println!("Vec slice: {:?}", vec_slice); }

Reference to Trait Objects

trait Drawable { fn draw(&self); } struct Circle { radius: f64, } impl Drawable for Circle { fn draw(&self) { println!("Drawing circle with radius {}", self.radius); } } struct Square { side: f64, } impl Drawable for Square { fn draw(&self) { println!("Drawing square with side {}", self.side); } } fn draw_shape(shape: &dyn Drawable) { shape.draw(); } fn main() { let circle = Circle { radius: 5.0 }; let square = Square { side: 3.0 }; // References to trait objects let drawables: [&dyn Drawable; 2] = [&circle, &square]; for drawable in drawables { drawable.draw(); } // Function taking trait object reference draw_shape(&circle); draw_shape(&square); // Boxed trait objects let boxed: Box<dyn Drawable> = Box::new(Circle { radius: 10.0 }); boxed.draw(); }

5. Reference Patterns

Reference Patterns in Match

fn main() { // Matching on references let maybe_value: Option<i32> = Some(5); let ref_to_option = &maybe_value; match ref_to_option { Some(value) => println!("Got value: {}", value), None => println!("Got none"), } // Using ref keyword in patterns let x = 5; let y = Some(x); match y { Some(ref value) => println!("Got ref to: {}", value), // value is &i32 None => (), } // ref mut for mutable references let mut z = Some(10); match z { Some(ref mut value) => { *value += 5; println!("Modified: {}", value); } None => (), } println!("z after: {:?}", z); // Matching on slices let arr = [1, 2, 3]; let slice = &arr[..]; match slice { [first, second, third] => println!("Three elements: {}, {}, {}", first, second, third), [first, ..] => println!("First element: {}", first), [] => println!("Empty slice"), } // Matching on strings let s = String::from("hello"); let s_ref = &s; match s_ref { s if s.starts_with('h') => println!("Starts with h: {}", s), _ => println!("Other"), } }

Reference Patterns in Destructuring

struct Point { x: i32, y: i32, } fn main() { let point = Point { x: 10, y: 20 }; let point_ref = &point; // Destructuring a reference let Point { x, y } = point_ref; println!("x: {}, y: {}", x, y); // x and y are &i32 // Using & in pattern to match references let &Point { x: x_val, y: y_val } = point_ref; println!("x_val: {}, y_val: {}", x_val, y_val); // x_val and y_val are i32 // With nested references let nested = (1, &point); match nested { (num, Point { x, y }) => { println!("num: {}, x: {}, y: {}", num, x, y); } } // Mutable references in destructuring let mut point = Point { x: 5, y: 10 }; let point_mut_ref = &mut point; let Point { x, y } = point_mut_ref; *x += 1; *y += 2; println!("point after: ({}, {})", point.x, point.y); }

6. Borrowing and Lifetimes

Basic Lifetime Annotations

// Lifetime annotations specify relationship between references fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } // Multiple lifetimes fn complex<'a, 'b>(x: &'a str, y: &'b str) -> &'a str { println!("y: {}", y); x // Return x with lifetime 'a } // Lifetime in struct 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 // Lifetime elided - same as self } } fn main() { let string1 = String::from("long string is long"); let result; { let string2 = String::from("xyz"); result = longest(&string1, &string2); println!("Longest: {}", result); // Valid here } // string2 dropped // println!("Longest: {}", result); // Would be invalid if used let excerpt; { let novel = String::from("Call me Ishmael. Some years ago..."); excerpt = ImportantExcerpt { part: &novel[..20], }; println!("Excerpt: {}", excerpt.part); } // novel dropped, excerpt invalid after this // println!("{:?}", excerpt.part); // Error: borrowed value doesn't live long enough }

Lifetime Elision Rules

// No explicit lifetimes needed - elision rules apply // Rule 1: Each parameter that is a reference gets its own lifetime parameter fn first_word(s: &str) -> &str { // Elided: <'a>(s: &'a str) -> &'a str let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] } // Rule 2: If there's exactly one input lifetime, it's assigned to all output lifetimes fn identity(x: &i32) -> &i32 { // Elided: <'a>(x: &'a i32) -> &'a i32 x } // Rule 3: If there are multiple input lifetimes, but one is &self or &mut self, // the lifetime of self is assigned to all output lifetimes struct Container { data: String, } impl Container { fn get_data(&self) -> &str { // Elided: <'a>(&'a self) -> &'a str &self.data } fn process(&self, other: &str) -> &str { // Elided: <'a>(&'a self, other: &str) -> &'a str if other.len() > self.data.len() { other // But wait, this doesn't work with elision! } else { &self.data } } // The above doesn't actually work because the elided lifetime would // force both return values to have lifetime 'a, but 'other' has a different lifetime // Correct version: fn process_fixed<'a>(&'a self, other: &str) -> &'a str { if other.len() > self.data.len() { &self.data // Return with lifetime 'a } else { &self.data } } }

7. Advanced Borrowing Patterns

Borrowing in Structs

// Struct containing references struct RefHolder<'a> { reference: &'a i32, } // Struct with multiple references struct Pair<'a, 'b> { first: &'a str, second: &'b str, } impl<'a, 'b> Pair<'a, 'b> { fn new(first: &'a str, second: &'b str) -> Self { Pair { first, second } } fn longest(&self) -> &str where 'a: 'b, // 'a outlives 'b { if self.first.len() > self.second.len() { self.first } else { self.second } } } // Struct with mutable reference struct MutHolder<'a> { value: &'a mut i32, } impl<'a> MutHolder<'a> { fn increment(&mut self) { *self.value += 1; } } fn main() { let x = 42; let holder = RefHolder { reference: &x }; println!("Value: {}", holder.reference); let s1 = String::from("hello"); let s2 = String::from("world"); let pair = Pair::new(&s1, &s2); println!("Longest: {}", pair.longest()); let mut num = 10; let mut holder = MutHolder { value: &mut num }; holder.increment(); holder.increment(); println!("num after: {}", num); }

Borrowing in Enums

// Enum with references enum JsonValue<'a> { Null, Boolean(bool), Number(f64), String(&'a str), Array(Vec<JsonValue<'a>>), Object(Vec<(&'a str, JsonValue<'a>)>), } // Enum with different reference types enum Either<'a, 'b> { Left(&'a str), Right(&'b str), } impl<'a, 'b> Either<'a, 'b> { fn as_str(&self) -> &str { match self { Either::Left(s) => s, Either::Right(s) => s, } } } fn main() { let s = "hello"; let json = JsonValue::String(s); match json { JsonValue::String(s) => println!("String: {}", s), _ => println!("Not a string"), } let left = Either::Left("left"); let right = Either::Right("right"); println!("Left: {}", left.as_str()); println!("Right: {}", right.as_str()); }

8. Interior Mutability

RefCell for Interior Mutability

use std::cell::RefCell; struct Messenger { messages: RefCell<Vec<String>>, } impl Messenger { fn new() -> Self { Messenger { messages: RefCell::new(Vec::new()), } } fn send(&self, message: &str) { // Borrow mutably even though self is immutable self.messages.borrow_mut().push(message.to_string()); } fn get_messages(&self) -> Vec<String> { self.messages.borrow().clone() } fn count(&self) -> usize { self.messages.borrow().len() } } // Runtime borrow checking fn main() { let messenger = Messenger::new(); messenger.send("Hello"); messenger.send("World"); println!("Message count: {}", messenger.count()); // Multiple borrows { let msgs = messenger.messages.borrow(); println!("Messages: {:?}", *msgs); // This would panic at runtime // let mut msgs_mut = messenger.messages.borrow_mut(); // panic! } messenger.send("Another message"); // Iterate through messages for msg in messenger.get_messages() { println!("Message: {}", msg); } }

RefCell with Rc for Multiple Owners

use std::rc::Rc; use std::cell::RefCell; #[derive(Debug)] struct Node { value: i32, children: RefCell<Vec<Rc<Node>>>, } impl Node { fn new(value: i32) -> Rc<Self> { Rc::new(Node { value, children: RefCell::new(vec![]), }) } fn add_child(node: &Rc<Node>, child: Rc<Node>) { node.children.borrow_mut().push(child); } fn print_tree(node: &Rc<Node>, depth: usize) { println!("{}{}", " ".repeat(depth), node.value); for child in node.children.borrow().iter() { Node::print_tree(child, depth + 1); } } } fn main() { let root = Node::new(1); let child1 = Node::new(2); let child2 = Node::new(3); let grandchild = Node::new(4); Node::add_child(&root, Rc::clone(&child1)); Node::add_child(&root, Rc::clone(&child2)); Node::add_child(&child1, grandchild); Node::print_tree(&root, 0); // Multiple owners possible with Rc let shared = Node::new(42); let owner1 = Rc::clone(&shared); let owner2 = Rc::clone(&shared); Node::add_child(&owner1, Node::new(100)); println!("Owner1 count: {}", owner1.children.borrow().len()); println!("Owner2 count: {}", owner2.children.borrow().len()); }

9. Borrowing and Iterators

Iterator References

fn main() { let v = vec![1, 2, 3, 4, 5]; // Iterate over references for item in &v { println!("Item: {}", item); // item is &i32 } // Iterate over mutable references let mut v2 = vec![1, 2, 3, 4, 5]; for item in &mut v2 { *item *= 2; } println!("Doubled: {:?}", v2); // Iterator adapters with references let v3 = vec![1, 2, 3, 4, 5]; let sum: i32 = v3.iter().sum(); // iter() gives references println!("Sum: {}", sum); let doubled: Vec<i32> = v3.iter().map(|&x| x * 2).collect(); println!("Doubled: {:?}", doubled); // Filter with references let evens: Vec<&i32> = v3.iter().filter(|&&x| x % 2 == 0).collect(); println!("Evens: {:?}", evens); // Chaining operations let result: Vec<i32> = v3 .iter() .filter(|&&x| x > 2) .map(|&x| x * x) .collect(); println!("Squares of numbers > 2: {:?}", result); }

Custom Iterator with References

struct Window<'a, T> { slice: &'a [T], size: usize, pos: usize, } impl<'a, T> Window<'a, T> { fn new(slice: &'a [T], size: usize) -> Self { Window { slice, size, pos: 0, } } } impl<'a, T> Iterator for Window<'a, T> { type Item = &'a [T]; fn next(&mut self) -> Option<Self::Item> { if self.pos + self.size <= self.slice.len() { let window = &self.slice[self.pos..self.pos + self.size]; self.pos += 1; Some(window) } else { None } } } fn main() { let data = vec![1, 2, 3, 4, 5, 6]; let windows = Window::new(&data, 3); for window in windows { println!("Window: {:?}", window); } // Using with other iterator methods let sums: Vec<i32> = Window::new(&data, 2) .map(|w| w.iter().sum()) .collect(); println!("Sums of pairs: {:?}", sums); }

10. Borrowing and Closures

Closure Capture Modes

fn main() { let s = String::from("hello"); // Closure captures by reference (Fn) let print = || println!("s: {}", s); print(); println!("Still have s: {}", s); // s still accessible let mut counter = 0; // Closure captures by mutable reference (FnMut) let mut increment = || { counter += 1; println!("counter: {}", counter); }; increment(); increment(); // println!("counter: {}", counter); // Can't borrow here // Closure captures by value (FnOnce) let consume = || { let s = s; // s moved here println!("Consumed: {}", s); }; consume(); // consume(); // Can't call twice // println!("s: {}", s); // s moved // Mixing capture modes let a = String::from("a"); let mut b = String::from("b"); let c = String::from("c"); let mixed = move || { println!("a: {}", a); // by value (due to move) b.push_str(" modified"); // by mutable reference println!("b: {}", b); drop(c); // by value }; // a, b, c moved into closure mixed(); // mixed(); // Can't call twice (FnOnce) }

Borrowing in Returned Closures

// Returning closure that borrows fn make_adder(x: i32) -> impl Fn(i32) -> i32 { // x is copied/moved into closure move |y| x + y } // Returning closure that borrows from environment (doesn't work) // fn make_printer(s: &str) -> impl Fn() + '_ { // || println!("{}", s) // Need to specify lifetime // } // Correct version with lifetime fn make_printer<'a>(s: &'a str) -> impl Fn() + 'a { move || println!("{}", s) } // Closure that borrows mutably fn make_counter() -> impl FnMut() -> i32 { let mut count = 0; move || { count += 1; count } } fn main() { let add_five = make_adder(5); println!("5 + 3 = {}", add_five(3)); let s = String::from("hello"); let printer = make_printer(&s); printer(); let mut counter = make_counter(); println!("Count: {}", counter()); println!("Count: {}", counter()); println!("Count: {}", counter()); }

11. Borrowing and Error Handling

References in Results

#[derive(Debug)] struct Data { value: i32, } impl Data { fn new(value: i32) -> Self { Data { value } } fn get_value(&self) -> i32 { self.value } } fn find_data(data: &[Data], target: i32) -> Option<&Data> { data.iter().find(|&item| item.value == target) } fn modify_data(data: &mut [Data], target: i32) -> Result<&mut Data, String> { if let Some(item) = data.iter_mut().find(|item| item.value == target) { Ok(item) } else { Err(format!("Data with value {} not found", target)) } } fn process_data(data: &[Data]) -> Result<Vec<i32>, Box<dyn std::error::Error>> { let mut results = Vec::new(); for item in data { if item.value < 0 { return Err(format!("Negative value: {}", item.value).into()); } results.push(item.value * 2); } Ok(results) } fn main() -> Result<(), Box<dyn std::error::Error>> { let data = vec![ Data::new(1), Data::new(2), Data::new(3), Data::new(4), ]; if let Some(item) = find_data(&data, 3) { println!("Found: {}", item.value); } let mut mutable_data = vec![ Data::new(10), Data::new(20), Data::new(30), ]; match modify_data(&mut mutable_data, 20) { Ok(item) => { item.value += 5; println!("Modified: {}", item.value); } Err(e) => println!("Error: {}", e), } let processed = process_data(&data)?; println!("Processed: {:?}", processed); Ok(()) }

12. Best Practices and Common Patterns

Borrowing Guidelines

// 1. Use references for read-only access fn read_only(data: &[i32]) -> i32 { data.iter().sum() } // 2. Use mutable references when you need to modify fn modify_in_place(data: &mut [i32]) { for item in data { *item *= 2; } } // 3. Return owned data when you need to give ownership fn create_data() -> Vec<i32> { vec![1, 2, 3, 4, 5] } // 4. Use slices for more flexible borrowing fn process_slice(data: &[i32]) -> i32 { data.iter().sum() } // 5. Avoid long-lived borrows fn process_with_temp() -> i32 { let data = vec![1, 2, 3, 4, 5]; // Borrow only as long as needed let sum = { let slice = &data[..]; slice.iter().sum() }; // Borrow ends here // Can modify data here sum } // 6. Use interior mutability sparingly use std::cell::RefCell; struct Cache { data: RefCell<Vec<i32>>, } impl Cache { fn new() -> Self { Cache { data: RefCell::new(Vec::new()), } } fn get(&self, index: usize) -> Option<i32> { self.data.borrow().get(index).copied() } fn set(&self, value: i32) { self.data.borrow_mut().push(value); } } fn main() { // 1. Read-only let data = vec![1, 2, 3]; let sum = read_only(&data); println!("Sum: {}", sum); // 2. Modify in place let mut data = vec![1, 2, 3]; modify_in_place(&mut data); println!("Modified: {:?}", data); // 3. Create owned data let new_data = create_data(); println!("New data: {:?}", new_data); // 4. Work with slices let slice = &data[1..3]; println!("Slice sum: {}", process_slice(slice)); // 5. Temporary borrow let result = process_with_temp(); println!("Result: {}", result); // 6. Interior mutability let cache = Cache::new(); cache.set(42); cache.set(100); println!("Cache[0]: {:?}", cache.get(0)); println!("Cache[1]: {:?}", cache.get(1)); }

Common Patterns

// Builder pattern with references struct QueryBuilder<'a> { table: &'a str, condition: Option<&'a str>, limit: Option<usize>, } impl<'a> QueryBuilder<'a> { fn new(table: &'a str) -> Self { QueryBuilder { table, condition: None, limit: None, } } fn condition(mut self, condition: &'a str) -> Self { self.condition = Some(condition); self } fn limit(mut self, limit: usize) -> Self { self.limit = Some(limit); self } fn build(&self) -> String { let mut query = format!("SELECT * FROM {}", self.table); if let Some(cond) = self.condition { query.push_str(&format!(" WHERE {}", cond)); } if let Some(limit) = self.limit { query.push_str(&format!(" LIMIT {}", limit)); } query } } // Visitor pattern with references trait Visitor<'a> { fn visit_str(&mut self, s: &'a str); fn visit_int(&mut self, i: i32); } struct Printer { output: String, } impl<'a> Visitor<'a> for Printer { fn visit_str(&mut self, s: &'a str) { self.output.push_str(&format!("String: {}\n", s)); } fn visit_int(&mut self, i: i32) { self.output.push_str(&format!("Int: {}\n", i)); } } fn visit_all<'a, V: Visitor<'a>>(visitor: &mut V, data: &[&'a str]) { for item in data { if let Ok(num) = item.parse::<i32>() { visitor.visit_int(num); } else { visitor.visit_str(item); } } } fn main() { // Builder pattern let query = QueryBuilder::new("users") .condition("age > 18") .limit(10) .build(); println!("Query: {}", query); // Visitor pattern let data = vec!["hello", "42", "world", "123"]; let mut printer = Printer { output: String::new(), }; visit_all(&mut printer, &data); println!("Visitor output:\n{}", printer.output); }

Conclusion

Rust's borrowing and reference system is a cornerstone of its memory safety guarantees:

Key Takeaways

  1. Borrowing Rules: At any time, you can have either:
  • One mutable reference
  • Any number of immutable references
  1. References:
  • &T: Immutable reference (shared borrow)
  • &mut T: Mutable reference (exclusive borrow)
  1. Lifetimes: Ensure references never outlive the data they point to
  2. Dereferencing: Automatic and explicit dereferencing with *
  3. Interior Mutability: RefCell<T> for runtime borrow checking

Best Practices

  • Prefer immutable references by default
  • Use mutable references only when mutation is needed
  • Keep borrow scopes small to minimize conflicts
  • Use slices (&[T]) for flexible borrowing
  • Consider interior mutability only when necessary
  • Understand lifetimes for complex reference relationships

Common Patterns

PatternUse Case
&selfRead-only methods
&mut selfModifying methods
&[T]Function parameters for sequences
&strString parameters
RefCell<T>Interior mutability
Rc<RefCell<T>>Shared mutable data

Rust's borrowing system provides compile-time guarantees that prevent entire classes of bugs, making it one of the language's most powerful features while requiring some adjustment for developers coming from other languages.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper