Module 2 • Lesson 9

References and Borrowing

📚 9 min read 💻 Free Course 🦀 nixus.pro

References: Borrow Without Owning

References let you use a value without taking ownership. The & creates a reference; using it is called borrowing.

fn length(s: &String) -> usize {
    s.len()  // s is borrowed - NOT dropped when this ends
}

fn main() {
    let s = String::from("hello world");
    let len = length(&s);  // Pass reference
    println!("'{}' has {} chars", s, len); // s still valid!
}

// Borrowing rules:
// 1. Any number of immutable references (&T) at once
// 2. OR exactly one mutable reference (&mut T)
// 3. Never both simultaneously

fn demo_rules() {
    let s = String::from("hi");
    let r1 = &s;
    let r2 = &s;
    let r3 = &s;
    println!("{} {} {}", r1, r2, r3); // Fine - all immutable
    // All references done here (NLL)

    let mut s2 = String::from("hi");
    let r = &mut s2;
    r.push_str(", world");
    println!("{}", r); // Fine - only one mutable ref
}

Why These Rules?

fn main() {
    // This pattern is unsafe in C++ - Rust prevents it:
    let mut v = vec![1, 2, 3];
    // let first = &v[0];  // immutable ref
    // v.push(4);          // WOULD invalidate first! vector may reallocate
    // println!("{}", first); // dangling pointer in C++!

    // Rust forces you to be safe:
    let first = &v[0];
    println!("first: {}", first); // use reference BEFORE mutating
    v.push(4); // Now safe - first is done being used

    // Auto-deref: Rust dereferences automatically
    let s = String::from("hello");
    let r = &s;
    println!("{}", r.len());  // Auto-deref: same as (*r).len()
    
    // Deref coercions:
    let s: String = String::from("hi");
    let _r: &str = &s;  // &String -> &str automatically
}

🎯 Practice

  1. Write fn count_vowels(s: &str) -> usize counting vowels without taking ownership
  2. Write fn summarize(s: &String) -> String returning first 50 chars
  3. Demonstrate the borrow checker: hold a &v[0] while trying to push to v - read the error

🎉 Key Takeaways