Skip to content

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 here

The 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ểm

TRONG 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: &strfat 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ộ string

String Literals là &str

rust
let s: &str = "hello world";  // String literal
//     ^^^^ → &'static str — lives for entire program

String 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 here

Array 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

ConceptSyntaxMeaning
Immutable Reference&TBorrow read-only, nhiều cùng lúc OK
Mutable Reference&mut TBorrow read-write, chỉ một cùng lúc
String Slice&strView into String, không own
Array Slice&[T]View into array/Vec, không own
Deref Coercion&String&strTự độ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()
    }
}