Refutability: Whether a Pattern Might Fail to Match

الگوها به دو شکل هستند: قابل‌رد (refutable) و غیرقابل‌رد (irrefutable). الگوهایی که برای هر مقدار ممکن مطابقت دارند غیرقابل‌رد هستند. به‌عنوان مثال، x در عبارت let x = 5;، زیرا x با هر چیزی مطابقت دارد و بنابراین نمی‌تواند از تطابق باز بماند. الگوهایی که ممکن است برای برخی مقادیر ممکن مطابقت نداشته باشند قابل‌رد هستند. به‌عنوان مثال، Some(x) در عبارت if let Some(x) = a_value، زیرا اگر مقدار در متغیر a_value None باشد به‌جای Some، الگوی Some(x) مطابقت نخواهد داشت.

پارامترهای تابع، عبارات let، و حلقه‌های for فقط می‌توانند الگوهای غیرقابل‌رد بپذیرند، زیرا برنامه نمی‌تواند کاری معنادار انجام دهد وقتی مقادیر مطابقت ندارند. عبارات if let و while let و عبارت let-else الگوهای قابل‌رد و غیرقابل‌رد را می‌پذیرند، اما کامپایلر درباره الگوهای غیرقابل‌رد هشدار می‌دهد زیرا به‌طور تعریف‌شده برای مدیریت شکست احتمالی طراحی شده‌اند: عملکرد شرطی در توانایی آن است که بسته به موفقیت یا شکست به‌طور متفاوت عمل کند.

به‌طور کلی، نباید نیازی به نگرانی در مورد تمایز بین الگوهای قابل‌رد و غیرقابل‌رد داشته باشید؛ با این حال، باید با مفهوم قابل‌رد بودن آشنا باشید تا بتوانید زمانی که آن را در یک پیام خطا می‌بینید، واکنش نشان دهید. در این موارد، باید یا الگو را تغییر دهید یا ساختاری که الگو را با آن استفاده می‌کنید، بسته به رفتار موردنظر کد تغییر دهید.

بیایید به مثالی نگاه کنیم که وقتی سعی می‌کنیم از یک الگوی قابل‌رد جایی که راست نیاز به یک الگوی غیرقابل‌رد دارد استفاده کنیم، و برعکس، چه اتفاقی می‌افتد. فهرست 19-8 یک عبارت let را نشان می‌دهد، اما برای الگو ما Some(x)، یک الگوی قابل‌رد مشخص کرده‌ایم. همان‌طور که ممکن است انتظار داشته باشید، این کد کامپایل نخواهد شد.

fn main() {
    let some_option_value: Option<i32> = None;
    let Some(x) = some_option_value;
}
Listing 19-8: تلاش برای استفاده از یک الگوی قابل‌رد با let

اگر مقدار some_option_value None باشد، مطابقت با الگوی Some(x) شکست خواهد خورد، به این معنا که الگو قابل‌رد است. با این حال، عبارت let فقط می‌تواند یک الگوی غیرقابل‌رد بپذیرد زیرا چیزی معتبر وجود ندارد که کد بتواند با مقدار None انجام دهد. در زمان کامپایل، راست شکایت می‌کند که ما سعی کرده‌ایم از یک الگوی قابل‌رد جایی که یک الگوی غیرقابل‌رد نیاز است استفاده کنیم:

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0005]: refutable pattern in local binding
 --> src/main.rs:3:9
  |
3 |     let Some(x) = some_option_value;
  |         ^^^^^^^ pattern `None` not covered
  |
  = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
  = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
  = note: the matched value is of type `Option<i32>`
help: you might want to use `let else` to handle the variant that isn't matched
  |
3 |     let Some(x) = some_option_value else { todo!() };
  |                                     ++++++++++++++++

For more information about this error, try `rustc --explain E0005`.
error: could not compile `patterns` (bin "patterns") due to 1 previous error

زیرا ما هر مقدار معتبری را با الگوی Some(x) پوشش ندادیم (و نمی‌توانستیم پوشش دهیم!)، راست به‌درستی یک خطای کامپایلر تولید می‌کند.

اگر یک الگوی قابل‌رد داشته باشیم جایی که یک الگوی غیرقابل‌رد نیاز است، می‌توانیم با تغییر کدی که از الگو استفاده می‌کند آن را رفع کنیم: به‌جای استفاده از let، می‌توانیم از if let استفاده کنیم. سپس اگر الگو مطابقت نداشته باشد، کد به‌سادگی از اجرای کد داخل آکولادها صرف‌نظر می‌کند و راهی برای ادامه معتبر فراهم می‌کند. فهرست 19-9 نشان می‌دهد که چگونه کد در فهرست 19-8 را رفع کنیم.

fn main() {
    let some_option_value: Option<i32> = None;
    if let Some(x) = some_option_value {
        println!("{x}");
    }
}
Listing 19-9: استفاده از if let و یک بلوک با الگوهای قابل‌رد به‌جای let

ما به کد یک مسیر خروجی دادیم! این کد اکنون کاملاً معتبر است. با این حال، اگر به if let یک الگوی غیرقابل‌رد (الگویی که همیشه مطابقت دارد)، مانند x، بدهیم، همان‌طور که در فهرست 19-10 نشان داده شده است، کامپایلر یک هشدار خواهد داد.

fn main() {
    if let x = 5 {
        println!("{x}");
    };
}
Listing 19-10: تلاش برای استفاده از یک الگوی غیرقابل‌رد با if let

راست شکایت می‌کند که استفاده از if let با یک الگوی غیرقابل‌رد منطقی نیست:

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
warning: irrefutable `if let` pattern
 --> src/main.rs:2:8
  |
2 |     if let x = 5 {
  |        ^^^^^^^^^
  |
  = note: this pattern will always match, so the `if let` is useless
  = help: consider replacing the `if let` with a `let`
  = note: `#[warn(irrefutable_let_patterns)]` on by default

warning: `patterns` (bin "patterns") generated 1 warning
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.39s
     Running `target/debug/patterns`
5

به این دلیل، بازوهای match باید از الگوهای قابل‌رد استفاده کنند، به‌جز بازوی آخر که باید با یک الگوی غیرقابل‌رد هر مقدار باقی‌مانده را مطابقت دهد. راست به ما اجازه می‌دهد از یک الگوی غیرقابل‌رد در یک match با تنها یک بازو استفاده کنیم، اما این نحو به‌ویژه مفید نیست و می‌تواند با یک عبارت ساده‌تر let جایگزین شود.

اکنون که می‌دانید کجا می‌توان از الگوها استفاده کرد و تفاوت بین الگوهای قابل‌رد و غیرقابل‌رد چیست، بیایید تمام نحوهایی که می‌توانیم برای ایجاد الگوها استفاده کنیم را بررسی کنیم.