Quiz
1. What does `&name` do when passed to `fn length(s: &String)`, and what is the state of name after the call?
2. Why does `fn f(s: &str)` accept a string literal, a &String, and a slice?
3. What compiler error do you get for `let r = &s;` inside a function that tries to return r where s is a local String — and why?
4. Why can't you mutate a variable through a shared reference even if the variable is declared `mut`?
Flashcards
Question
What does &T mean, and how does passing &T differ from passing T to a function?
Click to reveal answer
Answer
&T is a shared (read-only) reference: many simultaneous readers, no ownership transfer, no move. Passing T moves ownership into the callee (the caller loses it); passing &T lends read access for the call and the caller keeps the value. The signature declares the contract: consume vs read.
Question
What is dereferencing, and how often do you actually write it?
Click to reveal answer
Answer
*r reads the value behind a reference — but auto-deref almost always saves you: method calls resolve through Deref impls (String → str), and &String coerces to &str wherever expected (Deref coercion). You write * explicitly mainly for primitive pointer reads; the compiler handles the rest.
Question
Why is `fn f(s: &str)` preferred over `fn f(s: &String)`?
Click to reveal answer
Answer
&str accepts all string-ish inputs via deref coercion — literals (&'static str), &String, &mut String, and slices — with no caller ceremony, and it signals 'I only read text' rather than 'I need a String specifically'. &String needlessly narrows the API to one representation.
Question
What does Rust guarantee about references, and what errors enforce it?
Click to reveal answer
Answer
References are never dangling — the referent outlives the borrow, proven statically (zero runtime cost). Violations: E0106 missing lifetime on a signature that would return a local's borrow; E0597 'does not live long enough' for internals; the fix is moving ownership out, borrowing longer-lived data, or returning owned values.
Question
Is &T nullable? How do you express 'maybe absent'?
Click to reveal answer
Answer
No — safe Rust has no null references; &T always points at a live value. Absence is Option<&T> (or Option<T> for owned), so the compiler forces you to handle the empty case. This is why Rust needs no null-checks on references and why use-after-free is impossible in safe code.
Revision Notes
Key Takeaways
- 1. &T is a read-only lease: no ownership transfer, unlimited simultaneous readers, and the signature documents consume-vs-read
- 2. Deref coercion (&String → &str) plus auto-deref in method calls is why &str is the universal text parameter and explicit * is rare
- 3. The compiler guarantees references are never dangling — a borrow cannot outlive its referent (E0106/E0597) — with zero runtime cost
- 4. While a shared borrow is live, the referent cannot be mutated (E0502) — that read-stability invariant is what makes sharing safe, locally and across threads
- 5. No null references: absence is Option<&T>, enforced by the type system rather than checked at runtime
Interview Tips
- • "What does &T give you over passing T?" — no move (caller keeps ownership), no clone (one pointer), unlimited parallel readers; frame it as the signature declaring intent: consuming vs reading
- • "Why can't a function return a reference to a local?" — the borrow would outlive the referent (dangling); the compiler demands lifetime proof or rejects it; the fix is returning the owned value (a move), which costs only the 24-byte descriptor
- • "Deref coercion — give an example" — &String auto-coerces to &str at call sites, so fn f(s: &str) accepts literals and Strings alike; mention String: Deref<Target=str> explicitly to show the mechanism
- • "Are Rust references just pointers?" — physically one word, semantically a proof-carrying borrow: no null, no dangling, no mutation-while-borrowed — guarantees C pointers need runtimes/sanitizers to approximate
Cheat Sheet
Shared References (&T)
Contract in signatures:
| Signature | Meaning |
|---|---|
fn f(x: T) |
consumes — may drop/store/return the value |
fn f(x: &T) |
reads — caller keeps ownership; any number of such borrows coexist |
fn f(x: &mut T) |
exclusive write access (next topic) |
Borrow, don't move: length(&name) — no move, no clone; the borrow ends at the call, the caller's value is untouched. &T is Copy (copies the reference, not the data).
Dereference: *r reads the referent — but auto-deref covers method calls ((&name).len() → (*(&name)).len()), and Deref coercion turns &String into &str automatically. Hence fn f(s: &str) accepts literals, &String, and slices.
Guarantees: never dangling (E0106/E0597 prove referent-outlives-borrow) · never null (use Option<&T>) · no mutation of the referent while shared borrows live (E0502 enforces read-stability).
Rule of thumb: borrow for reading; move only when the callee must own; return owned values rather than references to locals.