مقدمه

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

سینتکس‌های مختلف الگوها

  • تطبیق با لیترال‌ها (Matching Literals): شما می‌توانید مستقیماً با یک مقدار لیترال مطابقت دهید. match x { 1 => ..., _ => ... }
  • تطبیق با متغیرهای نام‌گذاری شده (Matching Named Variables): یک متغیر نام‌گذاری شده در یک الگو، یک الگوی ردناپذیر است که با هر مقداری مطابقت پیدا کرده و آن مقدار را به متغیر bind می‌کند.
  • چندین الگو (Multiple Patterns): شما می‌توانید با استفاده از عملگر | (OR)، چندین الگو را در یک شاخه match ترکیب کنید. match x { 1 | 2 => ..., _ => ... }
  • تطبیق با بازه‌ها (Matching Ranges): با استفاده از ..= می‌توانید یک بازه از مقادیر را مطابقت دهید (این سینتکس فقط برای مقادیر عددی و char معتبر است). match x { 1..=5 => ..., _ => ... }
  • Destructuring: شما می‌توانید الگوها را برای از هم باز کردن (destructure) ساختار struct، enum، تاپل و اسلایس‌ها استفاده کنید.
Copy Icon src/main.rs
struct Point { x: i32, y: i32 }

fn main() {
    let p = Point { x: 0, y: 7 };
    let Point { x, y } = p; // Destructure using a let statement
    assert_eq!(0, x);

    match p {
        Point { x, y: 0 } => println!("On the x axis at {}", x),
        Point { x: 0, y } => println!("On the y axis at {}", y), // This arm matches
        Point { x, y } => println!("On neither axis: ({}, {})", x, y),
    }
}

نادیده گرفتن مقادیر در یک الگو

گاهی اوقات شما به تمام مقادیر یک الگو نیاز ندارید. Rust به شما اجازه می‌دهد تا بخش‌هایی از یک الگو را با استفاده از _ یا .. نادیده بگیرید.

  • `_`: یک مقدار واحد را نادیده می‌گیرد و به هیچ متغیری bind نمی‌شود.
  • `..`: چندین مقدار را در یک ساختار مانند struct یا enum نادیده می‌گیرد.
Copy Icon src/main.rs
fn foo(_: i32, y: i32) { // Ignore the first parameter
    println!("This code only uses the y parameter: {}", y);
}

struct Point { x: i32, y: i32, z: i32 }
let origin = Point { x: 0, y: 0, z: 0 };
match origin {
    Point { x, .. } => println!("x is {}", x), // Ignore y and z
}

الگوهای پیشرفته

در این بخش، با چند قابلیت پیشرفته‌تر الگوها در Rust آشنا می‌شویم. این قابلیت‌ها به شما اجازه می‌دهند تا تطبیق‌های پیچیده‌تر و دقیق‌تری را روی داده‌ها انجام دهید. از جمله این امکانات می‌توان به match guards (شرط‌های اضافی روی الگوها) و @ bindings (اتصال مقدار به متغیر هنگام تطبیق با یک الگو) اشاره کرد.

Match Guards

یک «نگهبان مچ» یا match guard یک شرط if اضافی است که بعد از یک الگو در یک شاخه match قرار می‌گیرد. این شاخه تنها در صورتی اجرا می‌شود که هم الگو مطابقت داشته باشد و هم شرط if برابر با true باشد.

Copy Icon src/main.rs
let num = Some(4);

match num {
    Some(x) if x % 2 == 0 => println!("The number {} is even", x),
    Some(x) => println!("The number {} is odd", x),
    None => (),
}

@ Bindings

عملگر @ به شما اجازه می‌دهد تا همزمان هم بررسی کنید که یک مقدار با یک الگو مطابقت دارد و هم آن مقدار را به یک متغیر جدید bind کنید.

Copy Icon src/main.rs
enum Message {
    Hello { id: i32 },
}

let msg = Message::Hello { id: 5 };

match msg {
    Message::Hello {
        id: id_variable @ 3..=7,
    } => println!("Found an id in range: {}", id_variable),
    Message::Hello { id: 10..=12 } => {
        println!("Found an id in another range")
    }
    Message::Hello { id } => println!("Found some other id: {}", id),
}

در اینجا، ما بررسی می‌کنیم که آیا فیلد id در بازه ۳ تا ۷ قرار دارد یا خیر. اگر اینطور باشد، مقدار آن را به متغیر id_variable نیز bind می‌کنیم تا بتوانیم از آن در بدنه شاخه استفاده کنیم.

در این درس، تمام سینتکس‌های مختلفی را که Rust برای pattern matching ارائه می‌دهد، مرور کردیم. این الگوها ابزارهای بسیار قدرتمند و گویایی هستند که به ما اجازه می‌دهند تا منطق‌های پیچیده‌ای را بر اساس ساختار داده‌ها پیاده‌سازی کنیم. با این درس، فصل «بررسی دقیق‌تر Pattern Matching» به پایان می‌رسد. در فصل بعدی، به سراغ «قابلیت‌های پیشرفته‌تر Rust» خواهیم رفت و با مفاهیمی مانند کد ناامن، traitهای پیشرفته و ماکروها آشنا خواهیم شد.