Bỏ qua để đến nội dung

If let và while let trong Rust

Bạn đã dùng match để xử lý Option và Result. Tuy vậy, phần lớn thời gian bạn chỉ thực sự quan tâm đến một mẫu và muốn bỏ qua mọi thứ còn lại. Viết cả một match với nhánh _ => {} chỉ để không làm gì thì vẫn chạy, nhưng hơi rườm rà so với nhu cầu. if let là lối tắt.

fn main() {
let age: Option<i32> = Some(30);
match age {
Some(value) => println!("Tuổi là {value}"),
_ => {}, // thực ra chúng ta không quan tâm đến trường hợp này
}
}

Nhánh _ => {} đó chẳng làm gì ngoài việc thỏa mãn quy tắc “phải xử lý mọi trường hợp” của match. Nếu tất cả những gì bạn muốn là làm gì đó với Some, thì chừng này code là quá nhiều cho một ý tưởng đơn giản.

fn main() {
let age: Option<i32> = Some(30);
if let Some(value) = age {
println!("Tuổi là {value}");
}
}

Kết quả:

Tuổi là 30
▶ Tự chạy thử

Hãy đọc nó là: “nếu age khớp với mẫu Some(value), chạy khối này, với value được gắn vào thứ nằm bên trong.” Nếu age là None, khối đơn giản là bị bỏ qua — không lỗi, không in gì cả.

if let có thể có else, giống như một if bình thường:

fn main() {
let age: Option<i32> = None;
if let Some(value) = age {
println!("Tuổi là {value}");
} else {
println!("Không có thông tin tuổi");
}
}
▶ Tự chạy thử

Nó hoạt động y như vậy với Result, thứ bạn đã gặp ở bài Result:

fn main() {
let input = "42";
if let Ok(number) = input.parse::<i32>() {
println!("Đã chuyển được: {number}");
} else {
println!("Không chuyển được chuỗi đó thành số");
}
}
▶ Tự chạy thử

Giống như if có else if, theo sau if let có thể là else if let. Rust lần lượt thử từng mẫu và chạy khối đầu tiên khớp. Cách này tiện khi bạn có vài chỗ để tìm một giá trị, theo thứ tự ưu tiên:

fn main() {
let nickname: Option<&str> = None;
let full_name: Option<&str> = Some("Ferris Crab");
if let Some(nick) = nickname {
println!("Chào {nick}!");
} else if let Some(name) = full_name {
println!("Xin chào, {name}.");
} else {
println!("Xin chào, người lạ.");
}
}

Kết quả:

Xin chào, Ferris Crab.
▶ Tự chạy thử

nickname là None, nên khối đầu tiên bị bỏ qua. full_name khớp với Some(name), nên khối đó chạy, và else cuối cùng không bao giờ được chạy tới. Bạn cũng có thể chen vào một điều kiện thông thường, như else if is_admin { ... }, ở bất cứ đâu trong chuỗi.

Mỗi biến chỉ tồn tại bên trong khối của riêng nó: nick không dùng được trong khối thứ hai, và name không dùng được sau khi chuỗi kết thúc. Nếu bạn cần giá trị đã mở ra cho phần còn lại của code, hãy xem let-else ở bài So khớp mẫu.

while let áp dụng cùng ý tưởng đó cho vòng lặp: cứ lặp chừng nào giá trị còn khớp với một mẫu. Nó đặc biệt tiện để lấy hết phần tử ra khỏi một collection:

fn main() {
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{top}");
}
}

Kết quả:

3
2
1
▶ Tự chạy thử

.pop() trả về Some(value) khi vẫn còn phần tử, và None khi vector đã rỗng — lúc đó while let tự động dừng vòng lặp. Không cần tự kiểm tra độ dài.

Dùng match khi bạn thực sự cần xử lý nhiều trường hợp theo những cách khác nhau. Dùng if let khi thực sự chỉ có một mẫu bạn quan tâm và mọi thứ khác chỉ cần bỏ qua. Nó không thay thế match — chỉ là cách viết ngắn hơn cho tình huống phổ biến “tôi chỉ quan tâm một nhánh”.

Bắt đầu với let mut numbers = vec![4, 8, 15]; và một biến cộng dồn total bằng 0. Dùng while let với .pop() để lấy từng số ra từ cuối, cộng mỗi số vào total, rồi in số đó và tổng mới. Gợi ý: total cần có mut, và total += n cộng thêm vào nó.

Xem lời giải
fn main() {
let mut numbers = vec![4, 8, 15];
let mut total = 0;
while let Some(n) = numbers.pop() {
total += n;
println!("lấy ra {n}, tổng bây giờ là {total}");
}
}

Kết quả:

lấy ra 15, tổng bây giờ là 15
lấy ra 8, tổng bây giờ là 23
lấy ra 4, tổng bây giờ là 27
▶ Tự chạy thử

.pop() lấy từ cuối, đó là lý do 15 được lấy ra đầu tiên.

Lưu let input = "17";. Nếu nó chuyển được thành u32, hãy in ra tuổi đó là người lớn (từ 18 trở lên) hay trẻ vị thành niên. Nếu không chuyển được, in ra rằng nó không phải là số. Hãy thử đổi input thành "abc" nữa. Gợi ý: đặt một if / else bình thường bên trong khối if let.

Xem lời giải
fn main() {
let input = "17";
if let Ok(age) = input.parse::<u32>() {
if age >= 18 {
println!("{age} tuổi là người lớn");
} else {
println!("{age} tuổi là trẻ vị thành niên");
}
} else {
println!("'{input}' không phải là số");
}
}

Kết quả:

17 tuổi là trẻ vị thành niên
▶ Tự chạy thử

Với input = "abc", việc chuyển đổi thất bại, nhánh else bên ngoài chạy, và nó in ra 'abc' không phải là số.

  • if let Mẫu = giá_trị { ... } chỉ chạy khối khi giá_trị khớp với Mẫu — mọi thứ khác đều lặng lẽ bị bỏ qua.
  • Thêm else để xử lý trường hợp không khớp, giống một if bình thường, và nối nhiều mẫu bằng else if let.
  • while let Mẫu = giá_trị { ... } lặp chừng nào giá trị còn khớp, rất hợp để lấy hết phần tử khỏi một collection bằng .pop().
  • Dùng if let/while let khi bạn chỉ quan tâm một mẫu; dùng match khi cần xử lý riêng biệt nhiều trường hợp.

Kiểm tra nhanh

1. Chuyện gì xảy ra khi if let Some(value) = age chạy mà age là None?

2. Khi nào nên dùng match thay vì if let?

3. Trong while let Some(top) = stack.pop() { ... }, khi nào vòng lặp dừng?

Điểm: 0 / 3