Borrowing in Depth
Quick Recap: What is Borrowing?
Section titled “Quick Recap: What is Borrowing?”Borrowing is Rust’s way of letting you have references to data without taking ownership of it. It’s similar to how JavaScript handles object references, but with strict rules enforced at compile time.
Types of References
Section titled “Types of References”In Rust, there are two types of references:
Immutable References
Section titled “Immutable References”let x = 5;
let y = &x; // y is an immutable reference to x
println!("{}", *y); // Dereference y to get the value
Mutable References
Section titled “Mutable References”let mut x = 5;
let y = &mut x; // y is a mutable reference to x
*y += 1; // Modify x through y
The Borrowing Rules
Section titled “The Borrowing Rules”Rust enforces three main rules for borrowing:
- You can have either one mutable reference or any number of immutable references
- References must always be valid
- No data races allowed
Example: Preventing Data Races
Section titled “Example: Preventing Data Races”let mut data = vec![1, 2, 3];
// This would cause a data race in JavaScript
let first = &data[0];
data.push(4); // Error: cannot borrow `data` as mutable because it is also borrowed as immutable
Non-Lexical Lifetimes (NLL)
Section titled “Non-Lexical Lifetimes (NLL)”Rust’s borrow checker is smart enough to know when a reference is no longer used:
let mut x = 5;
let y = &x; // First borrow
println!("{}", y); // Last use of y
let z = &mut x; // This is fine because y is no longer used
Borrowing and Functions
Section titled “Borrowing and Functions”Functions can take references as parameters:
fn calculate_length(s: &String) -> usize {
s.len()
}
let s = String::from("hello");
let len = calculate_length(&s);
Comparing to JavaScript
Section titled “Comparing to JavaScript”JavaScript’s approach to references is more relaxed:
const obj = { value: 5 };
const ref1 = obj; // Both variables reference the same object
const ref2 = obj; // Can have multiple references
ref1.value = 10; // Modifies the shared object
console.log(ref2.value); // 10
The Ref Pattern in JavaScript
Section titled “The Ref Pattern in JavaScript”JavaScript developers often use the ref pattern in React:
function Component() {
const inputRef = useRef(null);
return <input ref={inputRef} />;
}
Borrowing and Iterators
Section titled “Borrowing and Iterators”Rust’s iterators often use borrowing:
let v = vec![1, 2, 3];
for i in &v { // Borrow v
println!("{}", i);
}
The Borrow Checker
Section titled “The Borrow Checker”The borrow checker is Rust’s compile-time mechanism that ensures memory safety.
Interior Mutability
Section titled “Interior Mutability”Sometimes you need to mutate data through an immutable reference:
use std::cell::RefCell;
let data = RefCell::new(5);
*data.borrow_mut() += 1; // Mutate through an immutable reference
Common Borrowing Patterns
Section titled “Common Borrowing Patterns”Splitting Borrows
Section titled “Splitting Borrows”You can split a borrow into multiple parts:
let mut v = vec![1, 2, 3];
let (first, rest) = v.split_at_mut(1);
Self-Referential Structs
Section titled “Self-Referential Structs”Sometimes you need a struct that contains a reference to itself.
Conclusion
Section titled “Conclusion”Borrowing is a powerful feature that allows Rust to provide memory safety without garbage collection.