Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

All the Places Patterns Can Be Used

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

match Arms

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

#![allow(unused)]
fn main() {
match <em>VALUE</em> {
    <em>PATTERN</em> => <em>EXPRESSION</em>,
    <em>PATTERN</em> => <em>EXPRESSION</em>,
    <em>PATTERN</em> => <em>EXPRESSION</em>,
}
}

برای مثال، در این‌جا عبارت 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” بعداً در این فصل به‌طور مفصل بررسی خواهیم کرد.

let Statements

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

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

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

let PATTERN = EXPRESSION;

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

برای دیدن جنبه‌ی تطبیق الگو در let به‌طور واضح‌تر، به کد موجود در لیست ۱۹-۱ نگاه کنید که از یک الگو با let برای تجزیه‌ی یک tuple استفاده می‌کند.

fn main() {
    let (x, y, z) = (1, 2, 3);
}
Listing 19-1: استفاده از یک الگو برای تجزیه‌ی یک tuple و ساختن سه متغیر به‌صورت هم‌زمان

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

اگر تعداد عناصر در الگو با تعداد عناصر در tuple تطابق نداشته باشد، نوع کلی تطابق نخواهد داشت و با خطای کامپایل مواجه خواهیم شد. برای مثال، لیست ۱۹-۲ تلاشی برای تجزیه‌ی یک tuple با سه عنصر به دو متغیر را نشان می‌دهد، که کار نخواهد کرد.

fn main() {
    let (x, y) = (1, 2, 3);
}
Listing 19-2: ساخت نادرست الگویی که تعداد متغیرهای آن با تعداد عناصر در tuple هم‌خوانی ندارد

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

$ 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

برای رفع این خطا، می‌توانیم یک یا چند مقدار را با استفاده از _ یا .. نادیده بگیریم، همان‌طور که در بخش [«نادیده گرفتن مقادیر در یک الگو»][ignoring-values-in-a-pattern] خواهید دید. اگر مشکل این باشد که متغیرهای زیادی در الگو داریم، راه‌حل این است که برخی متغیرها را حذف کنیم تا تعداد متغیرها با تعداد عناصر در tuple برابر شود.

عبارات شرطی if let

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

لیست ۱۹-۳ نشان می‌دهد که همچنین می‌توانیم if let، else if، و else if let را ترکیب کنیم. این کار انعطاف‌پذیری بیشتری نسبت به یک عبارت match به ما می‌دهد، که در آن تنها می‌توانیم یک مقدار را با الگوها مقایسه کنیم. همچنین، Rust الزام نمی‌کند که شرایط در زنجیره‌ی if let، else if، و else if let به یکدیگر مرتبط باشند.

کد موجود در لیست ۱۹-۳ تعیین می‌کند که پس‌زمینه‌ی شما بر اساس مجموعه‌ای از بررسی‌ها چه رنگی باشد. برای این مثال، متغیرهایی با مقادیر hardcoded ایجاد کرده‌ایم که یک برنامه واقعی ممکن است از ورودی کاربر دریافت کند.

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-3: ترکیب 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

ساختار while let مشابه if let است و به ما این امکان را می‌دهد که یک حلقه‌ی while تا زمانی که یک الگو با موفقیت تطبیق یابد، اجرا شود. در لیست ۱۹-۴، یک حلقه‌ی while let را نشان می‌دهیم که منتظر دریافت پیام‌هایی است که بین نخ‌ها (threads) ارسال می‌شوند، اما در این مورد به جای بررسی یک مقدار از نوع Option، یک مقدار از نوع Result را بررسی می‌کنیم.

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-4: استفاده از یک حلقه‌ی while let برای چاپ مقادیر تا زمانی که rx.recv() مقدار Ok برگرداند

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

حلقه‌های for

In a for loop, the value that directly follows the keyword for is a pattern. For example, in for x in y, the x is the pattern. Listing 19-5 demonstrates how to use a pattern in a for loop to destructure, or break apart, a tuple as part of the for loop.

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

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

کدی که در لیست ۱۹-۵ آمده است، خروجی زیر را چاپ خواهد کرد:

$ 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

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

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) باشند. در بخش بعدی این دو مفهوم را بررسی خواهیم کرد.