Lesson 10 of 50 · rust
Borrowing and References
Duration: 15 minutes
Lesson 10 of 50 · rust
Duration: 15 minutes
&T allows you to read a value without ownership:\n\nrust\nfn calculate_length(s: &String) -> usize {\n s.len()\n} // s goes out of scope, but it doesn't own the String, so nothing is dropped\n\nfn main() {\n let s = String::from(\"hello\");\n let len = calculate_length(&s); // Pass reference, not ownership\n println!(\"The length of '{}' is {}\", s, len); // s is still valid!\n}\n\n\nThe reference lets the function read the data without taking ownership. After the function returns, the original owner still has the value.\n\n## Mutable References\n\n&mut T allows reading AND writing:\n\nrust\nfn append_world(s: &mut String) {\n s.push_str(\" world\");\n}\n\nfn main() {\n let mut s = String::from(\"hello\");\n append_world(&mut s); // Pass mutable reference\n println!(\"{}\", s); // \"hello world\"\n}\n\n\nMutable references give temporary write permission while the original owner retains ultimate ownership.\n\n## The Borrowing Rules\n\nRust enforces strict borrowing rules at compile time to prevent data races:\n\n### Rule 1: Multiple Immutable References\n\nYou can have multiple read-only references:\n\nrust\nfn main() {\n let s = String::from(\"hello\");\n let r1 = &s; // First reference\n let r2 = &s; // Second reference\n let r3 = &s; // Third reference\n println!(\"r1: {}, r2: {}, r3: {}\", r1, r2, r3); // All OK\n}\n\n\n### Rule 2: Only One Mutable Reference\n\nAt any time, you can have ONE mutable reference OR multiple immutable references, but not both:\n\nrust\nfn main() {\n let mut s = String::from(\"hello\");\n let r1 = &s; // Immutable reference\n let r2 = &s; // Another immutable\n // let m = &mut s; // ERROR! Can't have mutable while immutable refs exist\n println!(\"r1: {}, r2: {}\", r1, r2);\n}\n\n\n### Why This Rule?\n\nThis prevents data races. Consider this scenario in other languages:\n\nrust\n// Hypothetically without borrowing rules\nlet mut data = 10;\nlet ref1 = &data; // Reference\n// ... if another thread modifies data ...\ndata = 20;\nprintln!(\"ref1: {}\", ref1); // Undefined behavior!\n\n\nRust's rules prevent this at compile time.\n\n## Reference Scope\n\nReferences go out of scope when they're last used, not when declared:\n\nrust\nfn main() {\n let mut s = String::from(\"hello\");\n let r1 = &s;\n let r2 = &s;\n println!(\"r1: {}, r2: {}\", r1, r2); // Last use of r1 and r2\n \n // r1 and r2 are out of scope here\n let m = &mut s; // OK! No immutable refs in scope\n println!(\"m: {}\", m);\n}\n\n\n## Practical Example\n\nrust\nfn check_contains(text: &String, word: &str) -> bool {\n text.contains(word)\n}\n\nfn make_uppercase(text: &mut String) {\n *text = text.to_uppercase();\n}\n\nfn main() {\n let mut message = String::from(\"hello rust\");\n \n // Immutable borrow\n if check_contains(&message, \"rust\") {\n println!(\"Found rust!\");\n }\n \n // Mutable borrow\n make_uppercase(&mut message);\n println!(\"Message: {}\", message);\n}\n\n\n## Key Benefits\n\n1. No copies: References avoid expensive data duplication\n2. Memory safe: Compiler prevents use-after-free\n3. No garbage collector: Deterministic cleanup\n4. Zero-cost: References compile to pointers, no runtime overhead