Giao diện
Lifetimes & Advanced Borrowing Advanced
Sau khi hiểu Ownership & Borrowing cơ bản — Đây là level tiếp theo
📋 PREREQUISITE
Bạn PHẢI hiểu rõ Ownership - The Law và Borrowing & References trước khi đọc bài này.
Lifetimes — Generic Parameters cho References
Vấn đề: Compiler không biết reference sống bao lâu
rust
// ❌ KHÔNG COMPILE
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() { x } else { y }
}Compiler Error:
error[E0106]: missing lifetime specifier
--> src/main.rs:1:33
|
1 | fn longest(x: &str, y: &str) -> &str {
| ---- ---- ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value,
but the signature does not say whether it is borrowed from `x` or `y`
}Giải pháp: Lifetime Annotations
rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}Ý nghĩa: Return reference sẽ sống ít nhất là 'a — intersection của lifetimes của x và y.
Lifetime Visualization
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str
x: &'a str ─────────────────────────┐
│
├──▶ return: &'a str
│ (valid for min of both)
y: &'a str ─────────────────────────┘
Ví dụ cụ thể:
═══════════════════════════════════════════════════════════════════
fn main() {
let s1 = String::from("long string"); // ──┐ 's1 lifetime
{ // │
let s2 = String::from("short"); // ──┼─┐ 's2 lifetime
let result = longest(&s1, &s2); // │ │
println!("{}", result); // ✅ OK // │ │
} // ──┼─┘ s2 dropped
// result không còn valid ở đây vì // │
// nó có thể trỏ đến s2 (đã dropped) // │
} // ──┘ s1 dropped❌ Sai: Dùng result sau khi shorter lifetime kết thúc
rust
fn main() {
let s1 = String::from("long string");
let result;
{
let s2 = String::from("short");
result = longest(&s1, &s2); // result tied to s2's lifetime
} // s2 dropped
println!("{}", result); // ❌ ERROR: s2 does not live long enough
}Lifetime Elision Rules
Compiler tự động thêm lifetimes theo 3 rules để giảm boilerplate:
Rule 1: Mỗi input reference có lifetime riêng
rust
// Bạn viết:
fn first_word(s: &str) -> &str
// Compiler thấy:
fn first_word<'a>(s: &'a str) -> &str // (chưa xong)Rule 2: Nếu chỉ có 1 input lifetime → output dùng nó
rust
// Bạn viết:
fn first_word(s: &str) -> &str
// Compiler thấy (sau Rule 1 & 2):
fn first_word<'a>(s: &'a str) -> &'a str // ✅ CompleteRule 3: Nếu có &self hoặc &mut self → output dùng self's lifetime
rust
impl MyStruct {
// Bạn viết:
fn get_name(&self) -> &str
// Compiler thấy:
fn get_name<'a>(&'a self) -> &'a str
}Khi nào PHẢI viết explicit lifetime?
rust
// ❌ Elision rules không đủ — cần explicit
fn longest(x: &str, y: &str) -> &str // 2 inputs, which one?
// ✅ Phải chỉ định
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str'static Lifetime
'static = reference sống suốt chương trình:
rust
// String literals có 'static lifetime
let s: &'static str = "hello world"; // Embedded trong binary
// Tại sao?
// "hello world" được lưu trong .rodata section của binary
// Không bao giờ bị deallocate → sống mãi mãi'static trong Trait Bounds
rust
// T phải own data hoặc chỉ chứa 'static references
fn spawn_thread<T: Send + 'static>(task: T) {
std::thread::spawn(move || {
// task phải sống đủ lâu cho thread
});
}Non-Lexical Lifetimes (NLL)
Trước Rust 2018: Borrows kéo dài đến hết scope
rust
// Rust 2015 behavior
fn main() {
let mut data = vec![1, 2, 3];
let first = &data[0]; // immutable borrow bắt đầu
println!("{}", first);
// Trong Rust 2015: first vẫn "sống" đến cuối scope
data.push(4); // ❌ ERROR trong Rust 2015
}Rust 2018+: Borrows kết thúc tại lần sử dụng cuối
rust
// Rust 2018+ với NLL
fn main() {
let mut data = vec![1, 2, 3];
let first = &data[0]; // immutable borrow bắt đầu
println!("{}", first); // ← immutable borrow KẾT THÚC Ở ĐÂY
data.push(4); // ✅ OK trong Rust 2018+
println!("{:?}", data);
}NLL Control Flow Graph
Fat Pointers Internals
Slice &[T] — ptr + len
rust
let arr = [1, 2, 3, 4, 5];
let slice: &[i32] = &arr[1..4]; // [2, 3, 4]Array: arr = [1, 2, 3, 4, 5]
┌─────┬─────┬─────┬─────┬─────┐
Memory: │ 1 │ 2 │ 3 │ 4 │ 5 │
└─────┴─────┴─────┴─────┴─────┘
0 4 8 12 16 (byte offset)
▲
│
Slice: &arr[1..4]
┌─────────────┐
│ ptr ────────┘
├─────────────┤
│ len: 3 │
└─────────────┘
16 bytes total (fat pointer)Trait Object &dyn Trait — data_ptr + vtable_ptr
rust
trait Animal {
fn speak(&self);
}
struct Dog;
impl Animal for Dog {
fn speak(&self) { println!("Woof!"); }
}
fn main() {
let dog = Dog;
let animal: &dyn Animal = &dog; // fat pointer
animal.speak();
}Vtable Layout:
&dyn Animal (16 bytes fat pointer):
┌──────────────────┐
│ data_ptr ────────────▶ Dog instance (0 bytes, ZST)
├──────────────────┤
│ vtable_ptr ──────────▶ ┌─────────────────────┐
└──────────────────┘ │ drop_fn │ → Dog::drop
├─────────────────────┤
│ size: 0 │
├─────────────────────┤
│ align: 1 │
├─────────────────────┤
│ speak_fn ───────────│─▶ Dog::speak
└─────────────────────┘💡 VTABLE INSIGHT
Vtable chứa: drop function pointer, size, alignment, và tất cả trait method function pointers. Mỗi concrete type có một vtable cho mỗi trait nó implement.
Static dispatch (generics): Monomorphized, no vtable, inlined.
Dynamic dispatch (dyn Trait): Vtable lookup at runtime.
Advanced Borrowing Patterns
Reborrow Pattern
rust
fn main() {
let mut v = vec![1, 2, 3];
// Reborrow: &mut T → &mut T (với lifetime ngắn hơn)
let r1 = &mut v;
let r2 = &mut *r1; // reborrow, r1 tạm thời "frozen"
r2.push(4);
// r1 có thể dùng lại sau khi r2 hết scope
r1.push(5);
}Split Borrowing
rust
fn main() {
let mut data = [1, 2, 3, 4, 5];
// ❌ SAI: không thể có 2 &mut vào cùng array
// let (a, b) = (&mut data[0], &mut data[1]);
// ✅ ĐÚNG: split_at_mut tách thành 2 slices độc lập
let (left, right) = data.split_at_mut(2);
left[0] = 10;
right[0] = 30;
// data = [10, 2, 30, 4, 5]
}Struct với Mixed Lifetimes
rust
struct Context<'a, 'b> {
data: &'a str,
config: &'b str,
}
impl<'a, 'b> Context<'a, 'b> {
fn get_data(&self) -> &'a str {
self.data // Return reference với lifetime 'a, không phải 'self
}
}Lifetime Bounds
'b: 'a — 'b outlives 'a
rust
fn example<'a, 'b>(x: &'a str, y: &'b str) -> &'a str
where
'b: 'a // 'b sống lâu hơn hoặc bằng 'a
{
if x.len() > 0 { x } else { y } // y có thể return vì 'b >= 'a
}T: 'a — T sống ít nhất là 'a
rust
struct Ref<'a, T: 'a> {
data: &'a T,
}
// T: 'a nghĩa là nếu T chứa references, chúng phải sống >= 'aBảng Tóm tắt
| Concept | Syntax | Meaning |
|---|---|---|
| Lifetime param | 'a | Generic cho reference lifetime |
| Static | 'static | Sống suốt chương trình |
| Elision Rule 1 | fn f(x: &T) | Mỗi input có lifetime riêng |
| Elision Rule 2 | Single input | Output dùng input's lifetime |
| Elision Rule 3 | &self method | Output dùng self's lifetime |
| NLL | Non-Lexical | Borrow ends at last use |
| Fat pointer slice | &[T] | 16 bytes (ptr + len) |
| Fat pointer trait | &dyn Trait | 16 bytes (ptr + vtable) |
| Outlives | 'b: 'a | 'b sống >= 'a |