Giao diện
Borrowing & References Core Concept
"Mượn" data thay vì "lấy" — Giải pháp cho bài toán ownership phức tạp
Bài toán: Dùng data mà không muốn Move
Trong bài trước, chúng ta thấy vấn đề:
rust
fn calculate_length(s: String) -> usize {
s.len()
} // s bị dropped ở đây!
fn main() {
let s = String::from("hello");
let len = calculate_length(s); // s moved
// println!("{}", s); // ❌ ERROR: s đã bị move
}Giải pháp? Borrowing — mượn reference thay vì lấy ownership.
Immutable References (&T)
Cú pháp cơ bản
rust
fn calculate_length(s: &String) -> usize { // s là reference đến String
s.len()
} // s ra khỏi scope, nhưng KHÔNG drop data (vì không own)
fn main() {
let s = String::from("hello");
let len = calculate_length(&s); // Tạo reference, không move
println!("The length of '{}' is {}.", s, len); // ✅ s vẫn valid
}Memory Visualization
main() owns String
↓
┌─────────────────┐
s: String │ ptr ─────────────┼───────▶ "hello" (heap)
(OWNER) ├─────────────────┤
│ len: 5 │
│ cap: 5 │
└─────────────────┘
↑
│ reference (borrow)
│
calculate_length(): │
┌─────────┴───────┐
s: &String │ ptr ───────────┘
(BORROWER) └─────────────────┘
8 bytes (just a pointer)Key insight: Reference chỉ là pointer — không copy data, không own data.
❌ Cannot Modify Through Immutable Reference
rust
fn change(s: &String) {
s.push_str(", world"); // ❌ COMPILE ERROR!
}
fn main() {
let s = String::from("hello");
change(&s);
}Compiler Error:
error[E0596]: cannot borrow `*s` as mutable, as it is behind a `&` reference
--> src/main.rs:2:5
|
1 | fn change(s: &String) {
| ------- help: consider changing this to be a mutable reference: `&mut String`
2 | s.push_str(", world");
| ^^^^^^^^^^^^^^^^^^^^^ `s` is a `&` reference, so the data it refers to cannot be borrowed as mutable
}Mutable References (&mut T)
Cú pháp
rust
fn change(s: &mut String) { // Mutable reference
s.push_str(", world"); // ✅ OK - có thể modify
}
fn main() {
let mut s = String::from("hello"); // Biến phải là mut
change(&mut s); // Mutable borrow
println!("{}", s); // "hello, world"
}Quy tắc: Chỉ MỘT Mutable Reference
rust
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // ❌ COMPILE ERROR!
println!("{}, {}", r1, r2);
}Compiler Error:
error[E0499]: cannot borrow `s` as mutable more than once at a time
--> src/main.rs:5:14
|
4 | let r1 = &mut s;
| ------ first mutable borrow occurs here
5 | let r2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
6 |
7 | println!("{}, {}", r1, r2);
| -- first borrow later used hereThe Borrowing Rules (Reader-Writer Lock)
📜 QUY TẮC MƯỢN (Compile-time Enforcement)
Tại bất kỳ thời điểm nào, bạn có thể có MỘT TRONG HAI:
- Nhiều immutable references (
&T) — Many Readers - Một mutable reference duy nhất (
&mut T) — One Writer
KHÔNG BAO GIỜ cả hai cùng lúc!
Analogy: Reader-Writer Lock
┌─────────────────────────────────────────────────────────────────┐
│ READER-WRITER LOCK ANALOGY │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Database/Library Analogy: │
│ │
│ ┌─────────────────┐ │
│ │ DATA (Book) │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ✅ ALLOWED: Many Readers │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │Reader 1│ │Reader 2│ │Reader 3│ (concurrent reads) │ │
│ │ │ &T │ │ &T │ │ &T │ │ │
│ │ └────────┘ └────────┘ └────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ✅ ALLOWED: One Writer (exclusive access) │ │
│ │ ┌────────┐ │ │
│ │ │Writer │ (no readers allowed) │ │
│ │ │&mut T │ │ │
│ │ └────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ❌ FORBIDDEN: Readers + Writer simultaneously │ │
│ │ ┌────────┐ ┌────────┐ │ │
│ │ │Reader │ │Writer │ → DATA RACE! │ │
│ │ │ &T │ │&mut T │ │ │
│ │ └────────┘ └────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘✅ Valid: Nhiều Immutable References
rust
fn main() {
let s = String::from("hello");
let r1 = &s; // ✅
let r2 = &s; // ✅
let r3 = &s; // ✅
println!("{}, {}, {}", r1, r2, r3); // Tất cả là readers
}❌ Invalid: Immutable + Mutable cùng lúc
rust
fn main() {
let mut s = String::from("hello");
let r1 = &s; // Immutable borrow
let r2 = &s; // OK - nhiều immutable
let r3 = &mut s; // ❌ ERROR: mutable borrow khi có immutable
println!("{}, {}, {}", r1, r2, r3);
}Compiler Error:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:6:14
|
4 | let r1 = &s;
| -- immutable borrow occurs here
5 | let r2 = &s;
6 | let r3 = &mut s;
| ^^^^^^ mutable borrow occurs here
7 |
8 | println!("{}, {}, {}", r1, r2, r3);
| -- immutable borrow later used here✅ Valid: Sequential Borrows (Non-Lexical Lifetimes - NLL)
rust
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{}, {}", r1, r2); // r1, r2 last used here
// r1, r2 không còn được dùng → borrows kết thúc
let r3 = &mut s; // ✅ OK - immutable borrows đã kết thúc
println!("{}", r3);
}Dangling References — Compiler ngăn chặn thế nào?
❌ Code SAI: Dangling Reference
rust
fn dangle() -> &String { // ❌ COMPILE ERROR!
let s = String::from("hello");
&s // Return reference to local variable
} // s dropped here → reference trỏ đến memory đã free
fn main() {
let reference = dangle();
}Compiler Error: error[E0106]: missing lifetime specifier --> src/main.rs:1:16 | 1 | fn dangle() -> &String { | ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from } but there is no value for it to be borrowed from
### Memory Visualization: Tại sao Dangling Reference nguy hiểmTRONG dangle(): ═══════════════════════════════════════════════════════════════════
Stack Frame: dangle()
┌─────────────────┐ ┌─────────────────────┐
│ s: String │───────────▶│ h │ e │ l │ l │ o │
│ (local) │ └─────────────────────┘
└─────────────────┘
│
│ &s (reference)
▼
┌─────────────────┐
│ ptr ───────────┼──────────▶ (points to s)
└─────────────────┘
SAU dangle() returns: ═══════════════════════════════════════════════════════════════════
Stack Frame: dangle() [DEALLOCATED]
┌─────────────────┐ ┌─────────────────────┐
│ ████████████████ │ │ ░░░░░░░░░░░░░░░░░░░ │
│ ████████████████ │ │ FREED MEMORY! │
└─────────────────┘ └─────────────────────┘
↑
Returned reference: │
┌─────────────────┐ │
│ ptr ───────────┼─────────────────────┘
└─────────────────┘
↑
DANGLING POINTER! Trỏ đến garbage
### ✅ Code ĐÚNG: Return Owned Value
```rust
fn no_dangle() -> String { // Return owned String, không phải reference
let s = String::from("hello");
s // Ownership moved out
}
fn main() {
let s = no_dangle(); // ✅ s nhận ownership
println!("{}", s);
}Slices: View into Memory
String Slices (&str)
Slice là reference đến một phần của collection — không own data.
rust
fn main() {
let s = String::from("hello world");
let hello: &str = &s[0..5]; // Slice: bytes 0-4
let world: &str = &s[6..11]; // Slice: bytes 6-10
println!("{} {}", hello, world); // "hello world"
}Memory Visualization: Slice
STACK HEAP
┌─────────────────┐ ┌───────────────────────────┐
s: String │ ptr ──────────────────────▶ │ h │ e │ l │ l │ o │ │ w │...
├─────────────────┤ └───────────────────────────┘
│ len: 11 │ ↑ ↑
│ cap: 11 │ │ │
└─────────────────┘ │ │
│ │
┌─────────────────┐ │ │
hello: &str │ ptr ────────────┼──────────────┘ │
(FAT PTR) ├─────────────────┤ │
│ len: 5 │ │
└─────────────────┘ │
│
┌─────────────────┐ │
world: &str │ ptr ────────────┼──────────────────────────────┘
(FAT PTR) ├─────────────────┤
│ len: 5 │
└─────────────────┘Key insight: &str là fat pointer (16 bytes) = ptr + len.
Slice Syntax
rust
let s = String::from("hello");
let slice1 = &s[0..2]; // "he" — indices 0, 1
let slice2 = &s[..2]; // "he" — từ đầu đến index 2
let slice3 = &s[3..]; // "lo" — từ index 3 đến hết
let slice4 = &s[..]; // "hello" — toàn bộ stringString Literals là &str
rust
let s: &str = "hello world"; // String literal
// ^^^^ → &'static str — lives for entire programString literals được lưu trong binary → lifetime là 'static.
❌ Slice Prevents Mutation
rust
fn main() {
let mut s = String::from("hello world");
let word = &s[0..5]; // Immutable borrow (slice)
s.clear(); // ❌ ERROR: mutable borrow while immutable exists
println!("{}", word);
}Compiler Error:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:6:5
|
4 | let word = &s[0..5];
| - immutable borrow occurs here
5 |
6 | s.clear();
| ^^^^^^^^^ mutable borrow occurs here
7 |
8 | println!("{}", word);
| ---- immutable borrow later used hereArray Slices (&[T])
rust
fn main() {
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let slice: &[i32] = &arr[1..4]; // [2, 3, 4]
println!("{:?}", slice);
}Slices trong Functions
rust
// ✅ Best practice: Accept &str instead of &String
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &byte) in bytes.iter().enumerate() {
if byte == b' ' {
return &s[0..i];
}
}
&s[..] // Entire string if no space
}
fn main() {
let s = String::from("hello world");
let word = first_word(&s); // ✅ Works with &String (deref coercion)
let literal = "hello world";
let word2 = first_word(literal); // ✅ Works with &str directly
}Bảng Tóm tắt
| Concept | Syntax | Meaning |
|---|---|---|
| Immutable Reference | &T | Borrow read-only, nhiều cùng lúc OK |
| Mutable Reference | &mut T | Borrow read-write, chỉ một cùng lúc |
| String Slice | &str | View into String, không own |
| Array Slice | &[T] | View into array/Vec, không own |
| Deref Coercion | &String → &str | Tự động convert |
Bài tập: Fix Compiler Errors
💪 Bài 1: Fix multiple mutable borrows
rust
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
r1.push_str(" world");
r2.push_str("!");
}Đáp án
Sequential borrows:
rust
fn main() {
let mut s = String::from("hello");
{
let r1 = &mut s;
r1.push_str(" world");
} // r1 scope ends
let r2 = &mut s; // ✅ OK now
r2.push_str("!");
println!("{}", s); // "hello world!"
}Hoặc không dùng intermediary:
rust
fn main() {
let mut s = String::from("hello");
s.push_str(" world");
s.push_str("!");
println!("{}", s);
}💪 Bài 2: Fix dangling reference
rust
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}Đáp án
Cần lifetime annotation (sẽ học chi tiết sau):
rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}Hoặc return owned String để tránh lifetime complexity:
rust
fn longest(x: &str, y: &str) -> String {
if x.len() > y.len() {
x.to_string()
} else {
y.to_string()
}
}