blog.dopana

Back

Mặc dù match là công cụ mạnh mẽ và an toàn nhờ tính toàn diện (exhaustive), đôi khi bạn chỉ thực sự quan tâm đến duy nhất một trường hợp và muốn bỏ qua tất cả các trường hợp còn lại. Trong tình huống này, việc viết cả một khối match với nhánh _ => () sẽ tạo ra nhiều mã thừa (boilerplate).

Rust cung cấp cú pháp if let như một cách viết ngắn gọn và thanh thoát hơn.

Giải thích đơn giản (ELI5): Chiếc hộp quà may mắn#

Hãy tưởng tượng bạn có một hộp quà bí mật:

  • Trong hộp có thể có đồ chơi (Some(toy)), hoặc chiếc hộp hoàn toàn trống rỗng (None).
  • Bạn chỉ muốn mở quà nếu có đồ chơi để chơi ngay, còn nếu hộp rỗng thì bạn chẳng cần bận tâm làm gì cả.
graph TD
    Input["Giá trị đầu vào (Option / Enum)"] --> Check{"Khớp với mẫu chỉ định không?"}
    Check -- Đúng --> Bind["Trích xuất dữ liệu & Chạy khối lệnh if let"]
    Check -- Sai --> ElseCheck{"Có khối else không?"}
    ElseCheck -- Có --> ElseAction["Chạy khối lệnh else"]
    ElseCheck -- Không --> Ignore["Bỏ qua và tiếp tục"]

So sánh: match vs if let#

Giả sử chúng ta chỉ muốn in ra giá trị cấu hình nếu nó tồn tại ở dạng Some(u8):

Cách dùng match (dài dòng)#

src/main.rs
let config_max = Some(3u8);

match config_max {
    Some(max) => println!("Giá trị tối đa được cấu hình là: {max}"),
    _ => (), // Phải thêm nhánh này để thỏa mãn tính toàn diện của match
}
rust

Cách dùng if let (gọn gàng)#

src/main.rs
let config_max = Some(3u8);

if let Some(max) = config_max {
    println!("Giá trị tối đa được cấu hình là: {max}");
}
rust

[!TIP] Bạn có thể coi if let là cú pháp “đường cú pháp” (syntax sugar) cho match khi bạn chỉ thực thi code khi giá trị khớp với một mẫu duy nhất và bỏ qua mọi giá trị khác.

Kết hợp if let với else#

Chúng ta cũng có thể đính kèm một khối else (tương đương với nhánh _ => ... trong biểu thức match):

So sánh: if let vs if thông thường#

Nhiều người mới học Rust thường tự hỏi: Tại sao lại cần if let khi ta đã có câu lệnh if thông thường?

Điểm khác biệt cốt lõi nằm ở khớp mẫu và trích xuất dữ liệu (pattern matching & destructuring):

src/main.rs
let score = Some(95);

// 1. Dùng `if` thông thường: chỉ kiểm tra điều kiện Boolean (true/false)
if score.is_some() {
    // Phải unwrap thủ công để lấy giá trị ra -> Dễ nhầm lẫn hoặc dài dòng
    let val = score.unwrap();
    println!("Điểm số: {val}");
}

// 2. Dùng `if let`: vừa kiểm tra mẫu, vừa trích xuất và gán biến ngay lập tức
if let Some(val) = score {
    println!("Điểm số: {val}"); // An toàn tuyệt đối, không cần gọi .unwrap()
}
rust
graph TD
    subgraph IfOrdinary ["if thông thường"]
        Cond["Kiểm tra biểu thức Boolean (true/false)"] --> Unsafe["Cần unwrap thủ công nếu muốn lấy dữ liệu bên trong"]
    end
    subgraph IfLet ["if let"]
        Pattern["Khớp mẫu (Pattern Matching)"] --> Safe["Tự động trích xuất dữ liệu & tạo biến cục bộ an toàn"]
    end

Khi nào nên dùng if, if let, hay match?#

Tiêu chíif thông thườngif letmatch
Điều kiện kiểm traBiểu thức Boolean (bool)Mẫu cần khớp (Pattern)Toàn bộ các mẫu có thể có
Trích xuất dữ liệuKhông (phải unwrap thủ công)Có (tự động bóc tách & gán biến)Có (bóc tách dữ liệu theo từng nhánh)
Tính toàn diện (Exhaustive)KhôngKhông (chỉ khớp 1 mẫu)Có (bắt buộc kiểm tra 100% trường hợp)
Số nhánh rẽ1 (if) hoặc 2 (if/else)1 (if let) hoặc 2 (if let/else)Bất kỳ số lượng nhánh nào
Trường hợp sử dụng tốt nhấtSo sánh số, cờ trạng thái logicLấy dữ liệu từ Option/Enum với 1 mẫuXử lý đa trạng thái phức tạp, máy trạng thái

[!WARNING] Sử dụng if let đồng nghĩa với việc bạn từ bỏ sự kiểm tra toàn diện chặt chẽ của trình biên dịch mà match mang lại. Hãy cân nhắc xem liệu logic bài toán của bạn có nguy cơ bị bỏ sót trường hợp quan trọng nào không.

Tóm tắt#

  • if let giúp bạn viết gọn mã nguồn khi chỉ cần kiểm tra một mẫu khớp và trích xuất dữ liệu.
  • Bạn có thể kết hợp else để xử lý trường hợp không khớp.
  • Hãy chọn if let để loại bỏ mã thừa khi logic chỉ tập trung vào một biến thể duy nhất.

Tài liệu tham khảo#