Cấu trúc điều khiển match trong Rust
Tìm hiểu sức mạnh của match trong Rust: khớp mẫu toàn diện, bóc tách dữ liệu enum, xử lý Option và các mẫu bắt tất cả.
match là một trong những cấu trúc điều khiển luồng mạnh mẽ nhất trong Rust. Nó cho phép bạn so sánh một giá trị với một loạt các mẫu (patterns) và thực thi khối mã tương ứng dựa trên mẫu khớp đầu tiên.
Giải thích đơn giản (ELI5): Chiếc máy phân loại đồng xu#
Hãy tưởng tượng bạn có một chiếc máy phân loại tiền xu kỳ diệu:
- Bạn thả một đồng xu vào khe nhận tiền.
- Đồng xu trượt qua từng rãnh kiểm tra kích thước theo thứ tự.
- Khi gặp rãnh vừa khít với kích thước của nó, đồng xu rơi vào ngăn đó và kích hoạt hành động tương ứng.
- Mỗi đồng xu chỉ rơi vào đúng một ngăn duy nhất.
graph TD
Input["Giá trị đầu vào (Input Value)"] --> Arm1{"Mẫu 1 (Pattern 1) khớp?"}
Arm1 -- Đúng --> Action1["Thực thi Nhánh 1 & Trả về kết quả"]
Arm1 -- Sai --> Arm2{"Mẫu 2 (Pattern 2) khớp?"}
Arm2 -- Đúng --> Action2["Thực thi Nhánh 2 & Trả về kết quả"]
Arm2 -- Sai --> ArmCatch{"Mẫu mặc định (_) khớp?"}
ArmCatch -- Đúng --> ActionCatch["Thực thi Nhánh bắt tất cả"]
Trong Rust, biểu thức match hoạt động hệt như vậy, với một điểm ưu việt: trình biên dịch bắt buộc bạn phải bao quát toàn bộ các trường hợp có thể xảy ra (tính toàn diện - exhaustiveness).
Cú pháp cơ bản của match#
Mỗi nhánh trong match được gọi là một arm (nhánh khớp), bao gồm hai phần: một mẫu cần so khớp và mã lệnh cần thực thi khi khớp, ngăn cách bởi dấu suy ra =>.
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Đồng xu may mắn!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
fn main() {
let coin = Coin::Penny;
println!("Giá trị: {} cent", value_in_cents(coin));
}rust[!NOTE]
matchlà một biểu thức (expression), nghĩa là nó trả về giá trị. Kiểu dữ liệu trả về từ tất cả các nhánh trong cùng một khốimatchphải giống nhau.
Mẫu liên kết với giá trị (Patterns that Bind to Values)#
Một khả năng vượt trội của match là bóc tách (destructure) và liên kết dữ liệu được lưu trữ bên trong các biến thể của enum:
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
California,
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState), // Biến thể chứa dữ liệu bên trong
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("Đồng 25 cent thuộc bang: {state:?}!");
25
}
}
}
fn main() {
let quarter = Coin::Quarter(UsState::Alaska);
println!("Giá trị: {} cent", value_in_cents(quarter));
}rustKhi truyền vào Coin::Quarter(UsState::Alaska), biến state sẽ tự động liên kết với giá trị UsState::Alaska.
Khớp mẫu với Option<T>#
Rust không có null, thay vào đó sử dụng Option<T> (Some(T) hoặc None). match là phương thức cốt lõi và an toàn nhất để lấy giá trị T ra khỏi Some:
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
fn main() {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
println!("five + 1 = {six:?}");
println!("none + 1 = {none:?}");
}rustTính toàn diện (Exhaustiveness) và các mẫu bắt tất cả#
Trình biên dịch Rust sẽ báo lỗi ngay khi bạn bỏ sót bất kỳ trường hợp nào:
// Lỗi biên dịch: không xử lý trường hợp `None`!
fn plus_one_invalid(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}rustSử dụng biến bắt tất cả hoặc _#
Khi bạn chỉ muốn xử lý một vài trường hợp đặc biệt và gom toàn bộ trường hợp còn lại vào một hành động mặc định:
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
other => move_player(other), // Bắt tất cả các giá trị khác và sử dụng biến 'other'
}
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => reroll(), // Bắt tất cả giá trị khác nhưng không cần dùng giá trị đó
}
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => (), // Không làm gì cả (trả về unit tuple)
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(_num: u8) {}
fn reroll() {}rust[!TIP] Nhánh bắt tất cả (
otherhoặc_) phải luôn luôn nằm ở vị trí cuối cùng trong biểu thứcmatch, vì các nhánh được đánh giá tuần tự từ trên xuống dưới.
Tóm tắt#
matchso khớp một giá trị với các mẫu và thực thi nhánh tương ứng đầu tiên.- Các nhánh có thể liên kết và giải nén dữ liệu từ enum.
- Trình biên dịch bảo đảm tính toàn diện: bạn không bao giờ có thể quên xử lý một trường hợp nào.
- Sử dụng
_hoặc tên biến ở cuối để gom các giá trị còn lại khi cần thiết.