iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Borrowing & References

Borrowing is how Rust lets you use a value without taking ownership. &T is a shared (read) borrow; &mut T is an exclusive (write) borrow. The borrow checker enforces: one writer OR many readers, never both.

Borrow rules + lifetimes by example

EXAMPLE
// 1) Shared borrow — many readers, no writers
fn print_lengths(items: &Vec<String>) {
    for s in items {
        println!("{}: {}", s, s.len());
    }
}

fn main() {
    let names = vec![String::from("Ada"), String::from("Bo")];
    print_lengths(&names);    // borrow, names still usable after
    print_lengths(&names);    // borrow again — fine
    println!("{:?}", names);
}

// 2) Exclusive borrow — one writer, zero readers
fn add_one(items: &mut Vec<i32>) {
    items.push(1);
}

let mut nums = vec![1, 2, 3];
add_one(&mut nums);
add_one(&mut nums);
println!("{:?}", nums);

// 3) Cannot mix readers + writers
let mut v = vec![1, 2, 3];
let r = &v;
let m = &mut v;            // ERROR: cannot borrow as mutable while shared borrow lives
// println!("{:?}", r);

// 4) Lifetimes — &str references must outlive the function
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() > b.len() { a } else { b }
}

// 5) Lifetime elision — compiler infers in 90% of cases
// fn first_word(s: &str) -> &str  →  same as fn first_word<'a>(s: &'a str) -> &'a str
fn first_word(s: &str) -> &str {
    s.split_whitespace().next().unwrap_or("")
}

// 6) Structs holding references — must declare a lifetime
struct ParsedConfig<'a> {
    name:    &'a str,
    section: &'a str,
}

impl<'a> ParsedConfig<'a> {
    fn parse(input: &'a str) -> Self {
        let mut parts = input.splitn(2, '.');
        ParsedConfig {
            section: parts.next().unwrap_or(""),
            name:    parts.next().unwrap_or(""),
        }
    }
}

// 7) Dangling reference — caught at compile time
// fn dangle() -> &String {
//     let s = String::from("hi");
//     &s                              // ERROR — s drops at end of fn
// }

// 8) Common idiom — take &str over &String, slice over &Vec
fn greet(name: &str)         { println!("hi {}", name); }    // takes &str, &String, literal
fn first(items: &[i32]) -> i32 { items[0] }                  // takes slice or vec ref

// 9) NLL — borrows end at last use, not end of scope (since Rust 2018)
let mut v = vec![1, 2, 3];
let first = &v[0];
println!("first = {}", first);     // borrow ends here
v.push(4);                          // now legal — no live borrow

Why it matters

The borrow checker is a friend, not a gatekeeper. Once you internalise “one writer XOR many readers”, every confusing error makes sense — and every passing program is genuinely race-free.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
fn len(s: &String) -> usize { s.len() }

let s = String::from("hello");
let n = len(&s);     // borrow, not move
println!("{s} = {n}");
Try it Yourself »

Exercise

Borrow immutably.

fn len(s: String) -> usize { s.len() }

Test yourself

Q1. At any time you can have either…
Q2. A reference is created with…
Q3. A mutable reference is created with…

Discussion

Loading…