All the Places Patterns Can Be Used

الگوها در بسیاری از جاها در راست ظاهر می‌شوند، و شما از آن‌ها زیاد استفاده کرده‌اید بدون اینکه متوجه شوید! این بخش تمام جاهایی که الگوها معتبر هستند را بررسی می‌کند.

match Arms

همان‌طور که در فصل 6 بحث شد، ما از الگوها در بازوهای (arms) عبارات match استفاده می‌کنیم. به‌طور رسمی، عبارات match به‌صورت کلمه کلیدی match، یک مقدار برای مطابقت، و یک یا چند بازوی match که از یک الگو و یک عبارت برای اجرا در صورت مطابقت مقدار با الگوی آن بازو تشکیل شده‌اند، تعریف می‌شوند، مانند این:

match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
}

برای مثال، اینجا عبارت match از فهرست 6-5 است که بر روی یک مقدار Option<i32> در متغیر x مطابقت می‌دهد:

match x {
    None => None,
    Some(i) => Some(i + 1),
}

الگوها در این عبارت match شامل None و Some(i) هستند که در سمت چپ هر پیکان قرار دارند.

یکی از نیازمندی‌های عبارات match این است که باید به‌صورت کامل باشند، به این معنا که تمام حالات ممکن برای مقدار در عبارت match باید پوشش داده شوند. یکی از راه‌های اطمینان از اینکه همه حالات را پوشش داده‌اید این است که یک الگوی عمومی (catchall) برای بازوی آخر داشته باشید: برای مثال، یک نام متغیر که هر مقداری را مطابقت می‌دهد هرگز شکست نمی‌خورد و بنابراین تمام موارد باقی‌مانده را پوشش می‌دهد.

الگوی خاص _ هر چیزی را مطابقت می‌دهد، اما هرگز به یک متغیر متصل نمی‌شود، بنابراین اغلب در بازوی آخر match استفاده می‌شود. الگوی _ می‌تواند زمانی مفید باشد که بخواهید هر مقداری که مشخص نشده است را نادیده بگیرید، برای مثال. ما الگوی _ را در بخش “Ignoring Values in a Pattern” بعداً در این فصل به‌طور مفصل بررسی خواهیم کرد.

Conditional if let Expressions

در فصل 6 بحث کردیم که چگونه از عبارات if let عمدتاً به‌عنوان یک روش کوتاه‌تر برای نوشتن معادل یک match که فقط یک حالت را مطابقت می‌دهد استفاده کنیم. به‌صورت اختیاری، if let می‌تواند یک else متناظر داشته باشد که شامل کدی برای اجرا در صورت عدم مطابقت الگو در if let باشد.

فهرست 19-1 نشان می‌دهد که همچنین ممکن است عبارات if let، else if، و else if let را با هم ترکیب و تطبیق دهید. این کار به ما انعطاف بیشتری نسبت به یک عبارت match می‌دهد، که در آن فقط می‌توانیم یک مقدار برای مقایسه با الگوها بیان کنیم. همچنین، راست نیاز ندارد که شرایط در یک سری از بازوهای if let، else if، else if let به یکدیگر مرتبط باشند.

کد در فهرست 19-1 تعیین می‌کند که بر اساس یک سری بررسی برای چندین شرط، پس‌زمینه شما چه رنگی داشته باشد. برای این مثال، متغیرهایی با مقادیر سخت‌کدشده ایجاد کرده‌ایم که یک برنامه واقعی ممکن است از ورودی کاربر دریافت کند.

Filename: src/main.rs
fn main() {
    let favorite_color: Option<&str> = None;
    let is_tuesday = false;
    let age: Result<u8, _> = "34".parse();

    if let Some(color) = favorite_color {
        println!("Using your favorite color, {color}, as the background");
    } else if is_tuesday {
        println!("Tuesday is green day!");
    } else if let Ok(age) = age {
        if age > 30 {
            println!("Using purple as the background color");
        } else {
            println!("Using orange as the background color");
        }
    } else {
        println!("Using blue as the background color");
    }
}
Listing 19-1: ترکیب if let، else if، else if let، و else

اگر کاربر یک رنگ مورد علاقه مشخص کند، از آن رنگ به‌عنوان پس‌زمینه استفاده می‌شود. اگر هیچ رنگ مورد علاقه‌ای مشخص نشده باشد و امروز سه‌شنبه باشد، رنگ پس‌زمینه سبز است. در غیر این صورت، اگر کاربر سن خود را به‌عنوان یک رشته مشخص کند و بتوانیم آن را با موفقیت به یک عدد تبدیل کنیم، رنگ یا بنفش یا نارنجی است، بسته به مقدار عدد. اگر هیچ‌کدام از این شرایط صدق نکند، رنگ پس‌زمینه آبی خواهد بود.

این ساختار شرطی به ما امکان پشتیبانی از نیازهای پیچیده را می‌دهد. با مقادیر سخت‌کدشده‌ای که در اینجا داریم، این مثال پیام Using purple as the background color را چاپ خواهد کرد.

می‌توانید ببینید که if let نیز می‌تواند متغیرهای جدیدی را معرفی کند که متغیرهای موجود را به همان روشی که بازوهای match انجام می‌دهند، پوشش می‌دهند: خط if let Ok(age) = age یک متغیر جدید به نام age معرفی می‌کند که حاوی مقدار داخل حالت Ok است و متغیر موجود age را پوشش می‌دهد. این بدان معناست که باید شرط if age > 30 را در داخل آن بلوک قرار دهیم: نمی‌توانیم این دو شرط را به‌صورت if let Ok(age) = age && age > 30 ترکیب کنیم. متغیر جدید age که می‌خواهیم با 30 مقایسه کنیم تا شروع محدوده جدید با آکولاد معتبر نیست.

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

while let Conditional Loops

مشابه با ساختار if let، حلقه شرطی while let به یک حلقه while اجازه می‌دهد تا زمانی که یک الگو همچنان مطابقت دارد، اجرا شود. اولین بار یک حلقه while let را در فصل 17 دیدیم، جایی که از آن برای ادامه حلقه زدن تا زمانی که یک stream مقادیر جدید تولید می‌کرد استفاده کردیم. به‌طور مشابه، در فهرست 19-2 یک حلقه while let نشان داده می‌شود که منتظر پیام‌هایی است که بین نخ‌ها ارسال می‌شود، اما در این مورد یک Result را بررسی می‌کند به‌جای یک Option.

fn main() {
    let (tx, rx) = std::sync::mpsc::channel();
    std::thread::spawn(move || {
        for val in [1, 2, 3] {
            tx.send(val).unwrap();
        }
    });

    while let Ok(value) = rx.recv() {
        println!("{value}");
    }
}
Listing 19-2: استفاده از یک حلقه while let برای چاپ مقادیر تا زمانی که rx.recv() مقدار Ok را بازمی‌گرداند

این مثال مقادیر 1، 2، و 3 را چاپ می‌کند. وقتی که recv را در فصل 16 دیدیم، خطا را مستقیماً unwrap می‌کردیم یا با استفاده از یک حلقه for به‌عنوان یک iterator با آن تعامل داشتیم. با این حال، همان‌طور که فهرست 19-2 نشان می‌دهد، می‌توانیم از while let نیز استفاده کنیم، زیرا متد recv تا زمانی که فرستنده پیام‌ها تولید می‌کند مقدار Ok بازمی‌گرداند و سپس زمانی که طرف فرستنده قطع می‌شود یک مقدار Err تولید می‌کند.

for Loops

در یک حلقه for، مقداری که مستقیماً بعد از کلمه کلیدی for می‌آید یک الگو است. برای مثال، در عبارت for x in y مقدار x یک الگو است. فهرست 19-3 نشان می‌دهد که چگونه می‌توان از یک الگو در یک حلقه for برای تخریب (destructure) یا تجزیه یک tuple به‌عنوان بخشی از حلقه for استفاده کرد.

fn main() {
    let v = vec!['a', 'b', 'c'];

    for (index, value) in v.iter().enumerate() {
        println!("{value} is at index {index}");
    }
}
Listing 19-3: Using a pattern in a for loop to destructure a tuple

کد در فهرست 19-3 خروجی زیر را چاپ خواهد کرد:

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
     Running `target/debug/patterns`
a is at index 0
b is at index 1
c is at index 2

ما یک iterator را با استفاده از متد enumerate تطبیق می‌دهیم تا یک مقدار و شاخص آن مقدار را تولید کند، که در یک tuple قرار می‌گیرد. اولین مقدار تولیدشده tuple (0, 'a') است. وقتی این مقدار با الگوی (index, value) مطابقت داده می‌شود، مقدار index برابر با 0 و مقدار value برابر با 'a' خواهد بود، و اولین خط خروجی چاپ می‌شود.

let Statements

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

#![allow(unused)]
fn main() {
let x = 5;
}

هر بار که از یک عبارت let مانند این استفاده کرده‌اید، از الگوها استفاده کرده‌اید، حتی اگر متوجه آن نشده باشید! به‌طور رسمی، یک عبارت let به این شکل است:

let PATTERN = EXPRESSION;

در عبارات مانند let x = 5; با یک نام متغیر در محل PATTERN، نام متغیر فقط یک شکل ساده از یک الگو است. راست عبارت را با الگو مقایسه می‌کند و هر نامی که پیدا می‌کند را تخصیص می‌دهد. بنابراین در مثال let x = 5;، x الگویی است که به این معناست: «هر چیزی که در اینجا مطابقت دارد را به متغیر x اختصاص بده». چون نام x کل الگو است، این الگو به‌طور مؤثر به این معناست: «هر چیزی که هست را به متغیر x اختصاص بده».

برای مشاهده جنبه تطبیق الگو در let به‌صورت واضح‌تر، فهرست 19-4 را در نظر بگیرید، که از یک الگو با let برای تخریب یک tuple استفاده می‌کند.

fn main() {
    let (x, y, z) = (1, 2, 3);
}
Listing 19-4: Using a pattern to destructure a tuple and create three variables at once

اینجا، ما یک tuple را با یک الگو مطابقت می‌دهیم. راست مقدار (1, 2, 3) را با الگوی (x, y, z) مقایسه می‌کند و می‌بیند که مقدار با الگو مطابقت دارد، بنابراین راست 1 را به x، 2 را به y، و 3 را به z اختصاص می‌دهد. می‌توانید این الگوی tuple را به‌عنوان سه الگوی متغیر فردی که درون آن قرار دارند تصور کنید.

اگر تعداد عناصر در الگو با تعداد عناصر در tuple مطابقت نداشته باشد، کل نوع مطابقت نخواهد داشت و یک خطای کامپایلر دریافت خواهیم کرد. برای مثال، فهرست 19-5 یک تلاش برای تخریب یک tuple با سه عنصر به دو متغیر را نشان می‌دهد، که کار نخواهد کرد.

fn main() {
    let (x, y) = (1, 2, 3);
}
Listing 19-5: Incorrectly constructing a pattern whose variables don’t match the number of elements in the tuple

تلاش برای کامپایل این کد منجر به این خطای type می‌شود:

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0308]: mismatched types
 --> src/main.rs:2:9
  |
2 |     let (x, y) = (1, 2, 3);
  |         ^^^^^^   --------- this expression has type `({integer}, {integer}, {integer})`
  |         |
  |         expected a tuple with 3 elements, found one with 2 elements
  |
  = note: expected tuple `({integer}, {integer}, {integer})`
             found tuple `(_, _)`

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

برای رفع خطا، می‌توانیم یک یا چند مقدار در tuple را با استفاده از _ یا .. نادیده بگیریم، همان‌طور که در بخش “Ignoring Values in a Pattern” خواهید دید. اگر مشکل این است که تعداد متغیرها در الگو بیش از حد است، راه‌حل این است که نوع‌ها را با حذف متغیرها طوری تطبیق دهیم که تعداد متغیرها برابر با تعداد عناصر در tuple شود.

Function Parameters

پارامترهای تابع نیز می‌توانند الگو باشند. کد در فهرست 19-6، که تابعی به نام foo را تعریف می‌کند که یک پارامتر به نام x از نوع i32 می‌گیرد، باید تا الان آشنا به نظر برسد.

fn foo(x: i32) {
    // code goes here
}

fn main() {}
Listing 19-6: یک امضای تابع از الگوها در پارامترها استفاده می‌کند

قسمت x یک الگو است! همان‌طور که با let انجام دادیم، می‌توانیم یک tuple را در آرگومان‌های یک تابع با الگو مطابقت دهیم. فهرست 19-7 مقادیر یک tuple را هنگام ارسال به یک تابع تجزیه می‌کند.

Filename: src/main.rs
fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({x}, {y})");
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
}
Listing 19-7: یک تابع با پارامترهایی که یک tuple را تخریب می‌کنند

این کد پیام Current location: (3, 5) را چاپ می‌کند. مقادیر &(3, 5) با الگوی &(x, y) مطابقت دارند، بنابراین x مقدار 3 و y مقدار 5 است.

ما همچنین می‌توانیم از الگوها در لیست پارامترهای closureها به همان روشی که در لیست پارامترهای تابع استفاده می‌کنیم، استفاده کنیم، زیرا closureها شبیه به توابع هستند، همان‌طور که در فصل 13 بحث شد.

تا اینجا، چندین روش برای استفاده از الگوها را دیده‌اید، اما الگوها در هر جایی که از آن‌ها استفاده کنیم به یک شکل کار نمی‌کنند. در برخی مکان‌ها، الگوها باید غیرقابل‌رد (irrefutable) باشند؛ در شرایط دیگر، می‌توانند قابل‌رد (refutable) باشند. در بخش بعدی این دو مفهوم را بررسی خواهیم کرد.