Borrowing in Depth
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
In Rust, there are two types of references:
Immutable References
let x = 5;let y = &x; // y is an immutable reference to xprintln!("{}", *y); // Dereference y to get the value
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
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
let mut data = vec![1, 2, 3];
// This would cause a data race in JavaScriptlet first = &data[0];data.push(4); // Error: cannot borrow `data` as mutable because it is also borrowed as immutable
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 borrowprintln!("{}", y); // Last use of ylet z = &mut x; // This is fine because y is no longer used
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
JavaScript’s approach to references is more relaxed:
const obj = { value: 5 };const ref1 = obj; // Both variables reference the same objectconst ref2 = obj; // Can have multiple references
ref1.value = 10; // Modifies the shared objectconsole.log(ref2.value); // 10
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
Rust’s iterators often use borrowing:
let v = vec![1, 2, 3];for i in &v { // Borrow v println!("{}", i);}
The Borrow Checker
The borrow checker is Rust’s compile-time mechanism that ensures memory safety.
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
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
Sometimes you need a struct that contains a reference to itself.
Conclusion
Borrowing is a powerful feature that allows Rust to provide memory safety without garbage collection.