⏰ Lifetimes: Dữ Liệu Sống Bao Lâu?
🎯 Mục Tiêu Bài Học
Sau khi hoàn thành bài học này, bạn sẽ:
- ✅ Hiểu được lifetime là gì và tại sao cần lifetimes
- ✅ Đọc và hiểu lifetime annotations (
'a,'b) - ✅ Sử dụng lifetimes trong functions
- ✅ Sử dụng lifetimes trong structs
- ✅ Nắm vững lifetime elision rules
- ✅ Xử lý được lỗi borrow checker liên quan đến lifetimes
🤔 Lifetime Là Gì?
Ẩn Dụ Cuộc Sống: Thẻ Vào Cửa Hạn Ngày
Lifetime giống như thẻ vào cửa công ty có hạn sử dụng:
🎫 Thẻ Vào Cửa:
- Có ngày bắt đầu và ngày hết hạn
- Chỉ valid trong khoảng thời gian đó
- Không được dùng sau khi hết hạn
📦 Reference Trong Rust:
- Có "thời gian sống" (lifetime)
- Chỉ valid khi dữ liệu gốc còn tồn tại
- Không thể outlive (sống lâu hơn) dữ liệu gốc
Tại Sao Cần Lifetimes?
Rust cần biết references sẽ sống bao lâu để đảm bảo:
- ✅ Không có dangling references (trỏ đến dữ liệu đã bị drop)
- ✅ Mọi reference đều valid khi sử dụng
- ✅ An toàn bộ nhớ mà không cần garbage collector
fn main() {
let r;
{
let x = 5;
r = &x; // ❌ Lỗi! x sẽ bị drop, r sẽ dangling
}
println!("{}", r);
}
Lỗi:
error[E0597]: `x` does not live long enough
Giải thích:
xchỉ sống trong inner scopercố gắng sống lâu hơn- Rust ngăn chặn vì sẽ unsafe!
🏷️ Lifetime Annotations
Cú Pháp Cơ Bản
Lifetime annotations bắt đầu với dấu ' (apostrophe):
&i32 // reference
&'a i32 // reference với lifetime 'a
&'a mut i32 // mutable reference với lifetime 'a
Quy ước:
'a,'b,'c→ Tên lifetime ngắn gọn'static→ Lifetime đặc biệt (sống suốt chương trình)
Lifetime Không Thay Đổi Thời Gian Sống
Quan trọng: Lifetime annotations không thay đổi thời gian sống thực tế!
Chúng chỉ:
- 📝 Mô tả mối quan hệ giữa lifetimes
- ✅ Giúp compiler kiểm tra tính hợp lệ
- 🚫 Không kéo dài hoặc rút ngắn lifetime
Giống như ghi ngày hết hạn lên thẻ → Không làm thẻ sống lâu hơn, chỉ ghi nhận thực tế!
🔧 Lifetimes Trong Functions
Ví Dụ Cần Lifetime
// ❌ Lỗi - thiếu lifetime annotation
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
Lỗi:
error[E0106]: missing lifetime specifier
Tại sao lỗi?:
- Compiler không biết return
xhayy - Không biết return value sẽ sống bao lâu
- Cần explicit lifetime annotation
Thêm Lifetime Annotation
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("hello");
let string2 = String::from("world");
let result = longest(&string1, &string2);
println!("Dài nhất: {}", result);
}
Giải thích:
<'a>→ Khai báo lifetime parameterx: &'a str→xcó lifetime'ay: &'a str→ycó lifetime'a-> &'a str→ Return value cũng có lifetime'a
Ý nghĩa:
- Return value sẽ sống ít nhất bằng lifetime ngắn nhất trong
xvày - Nếu
xsống 5s,ysống 10s → return s ẽ sống 5s
Lifetime Scope Example
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string");
{
let string2 = String::from("short");
let result = longest(&string1, &string2);
println!("{}", result); // ✅ OK - result dùng trong scope
}
// println!("{}", result); // ❌ Lỗi - result đã hết scope
}
Lỗi Lifetime Mismatch
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string");
let result;
{
let string2 = String::from("xyz");
result = longest(&string1, &string2);
} // string2 drop ở đây
println!("{}", result); // ❌ Lỗi!
}
Lỗi:
error[E0597]: `string2` does not live long enough
Giải thích:
resultcố gắng sống lâu hơnstring2- Nhưng
resultcó thể trỏ đếnstring2 - Rust ngăn chặn!
🏗️ Lifetimes Trong Structs
Struct Chứa References
struct Excerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().unwrap();
let excerpt = Excerpt {
part: first_sentence,
};
println!("Trích dẫn: {}", excerpt.part);
}
Giải thích:
Excerpt<'a>→ Struct có lifetime parameterpart: &'a str→ Field là reference với lifetime'a- Instance của
Excerptkhông thể outlivenovel
Lỗi Struct Lifetime
struct Excerpt<'a> {
part: &'a str,
}
fn main() {
let excerpt;
{
let novel = String::from("Some text");
excerpt = Excerpt { part: &novel };
} // novel drop ở đây
// println!("{}", excerpt.part); // ❌ Lỗi!
}
Methods Với Lifetimes
struct Excerpt<'a> {
part: &'a str,
}
impl<'a> Excerpt<'a> {
fn level(&self) -> i32 {
3
}
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Chú ý! {}", announcement);
self.part
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first = novel.split('.').next().unwrap();
let excerpt = Excerpt { part: first };
println!("Level: {}", excerpt.level());
println!("Part: {}", excerpt.announce_and_return_part("Bắt đầu!"));
}
Chú ý:
impl<'a> Excerpt<'a>→ Impl block cũng cần lifetime- Method có thể có lifetime riêng hoặc dùng lifetime của struct
🎓 Lifetime Elision Rules
Quy Tắc Tự Động
Trong nhiều trường hợp, Rust tự suy luận lifetimes mà không cần explicit annotations.
Ba quy tắc elision:
1️⃣ Mỗi parameter có lifetime riêng:
fn foo(x: &str) -> &str
// Rust hiểu thành:
fn foo<'a>(x: &'a str) -> &str
2️⃣ Nếu có đúng 1 input lifetime → output dùng lifetime đó:
fn first_word(s: &str) -> &str
// Rust hiểu thành:
fn first_word<'a>(s: &'a str) -> &'a str
3️⃣ Nếu có &self hoặc &mut self → output dùng lifetime của self:
impl Excerpt {
fn part(&self) -> &str
// Rust hiểu thành:
fn part<'a>(&'a self) -> &'a str
}
Ví Dụ Không Cần Annotation
// ✅ Không cần lifetime - Rule 2
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[..i];
}
}
&s[..]
}
// ✅ Không cần lifetime - Rule 3
impl<'a> Excerpt<'a> {
fn get_part(&self) -> &str {
self.part
}
}
Khi Nào Cần Explicit Lifetimes?
// ❌ Cần explicit - nhiều inputs, không rõ output liên quan với input nào
fn longest(x: &str, y: &str) -> &str {
// ...
}
// ✅ Thêm lifetime
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
🌟 The Static Lifetime
'static - Lifetime Đặc Biệt
fn main() {
// String literal có lifetime 'static
let s: &'static str = "Hello, world!";
println!("{}", s);
}
Đặc điểm 'static:
- Sống suốt chương trình
- Lưu trong binary
- Luôn valid, không bao giờ bị drop
Khi Nào Dùng 'static?
// ✅ String literals
let s: &'static str = "I'm static!";
// ✅ Constants
const MAX: i32 = 100;
static NAME: &str = "Rust";
// ❌ Không nên dùng 'static để "fix" lỗi!
// Thường có cách tốt hơn
Cảnh báo: Đừng dùng 'static chỉ để compiler im lặng! Thường có vấn đề design.
🎯 Ví Dụ Thực Tế
Ví Dụ 1: So Sánh Strings
fn compare<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let s1 = String::from("Rust");
let s2 = "Programming";
let longer = compare(&s1, s2);
println!("Dài hơn: {}", longer);
}
Ví Dụ 2: Parser
struct Parser<'a> {
text: &'a str,
position: usize,
}
impl<'a> Parser<'a> {
fn new(text: &'a str) -> Self {
Parser { text, position: 0 }
}
fn next_word(&mut self) -> Option<&'a str> {
let start = self.position;
while self.position < self.text.len() {
if self.text.as_bytes()[self.position] == b' ' {
let word = &self.text[start..self.position];
self.position += 1;
return Some(word);
}
self.position += 1;
}
if start < self.text.len() {
Some(&self.text[start..])
} else {
None
}
}
}
fn main() {
let text = String::from("hello rust programming");
let mut parser = Parser::new(&text);
while let Some(word) = parser.next_word() {
println!("Từ: {}", word);
}
}
Đầu ra:
Từ: hello
Từ: rust
Từ: programming
Ví Dụ 3: Config Holder
struct Config<'a> {
name: &'a str,
version: &'a str,
}
impl<'a> Config<'a> {
fn new(name: &'a str, version: &'a str) -> Self {
Config { name, version }
}
fn display(&self) {
println!("{} v{}", self.name, self.version);
}
}
fn main() {
let app_name = String::from("MyApp");
let app_version = "1.0.0";
let config = Config::new(&app_name, app_version);
config.display();
}
Đầu ra:
MyApp v1.0.0
Ví Dụ 4: Multiple Lifetimes
fn mix<'a, 'b>(x: &'a str, y: &'b str, use_first: bool) -> &'a str {
if use_first {
x
} else {
x // Phải return 'a, không thể return 'b
}
}
fn main() {
let s1 = String::from("first");
let s2 = String::from("second");
let result = mix(&s1, &s2, true);
println!("{}", result);
}
Giải thích:
'avà'blà hai lifetimes độc lập- Return type là
&'a→ chỉ có thể returnx - Nếu muốn return
xhoặcy, cần cùng lifetime