পাঠ ১৯.১

Pattern কোথায় কোথায় ব্যবহার হয়

All the Places Patterns Can Be Used

Pattern Rust-এর অনেক জায়গায় ছড়িয়ে আছে — তুমি না বুঝেই এগুলো অনেকবার use করেছ। এই পাঠে দেখব pattern কোথায় কোথায় valid।

match arm

চ্যাপ্টার ৬-এ আমরা match-এর arm-এ pattern use করেছি। Formally — match-এর structure এমন:

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

চ্যাপ্টার ৬-এর একটা example:

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

এখানে pattern হলো None এবং Some(i)

match-এ একটা rule — সব possibility cover করতে হবে (exhaustive)। Last arm-এ catch-all pattern রাখলে এটা নিশ্চিত। বিশেষভাবে _ pattern — match করে সবকিছুর সাথে, কিন্তু variable bind করে না; তাই ignore-এর জন্য আদর্শ। বিস্তারিত পরের পাঠে।

let statement

এই অধ্যায়ের আগে আমরা শুধু match এবং if let-এ pattern নিয়ে কথা বলেছি, কিন্তু আসলে let-ও pattern use করে।

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

সাধারণ let x = 5;-এ x একটা pattern — সবচেয়ে simple form। Formally:

let PATTERN = EXPRESSION;

Variable name একটা trivial pattern — "এই value-কে x নামক variable-এ bind করো"।

Tuple destructure-এ pattern-এর শক্তি আরো স্পষ্ট:

fn main() {
    let (x, y, z) = (1, 2, 3);
}

Rust value (1, 2, 3)-কে pattern (x, y, z)-এর সাথে match করে — element সংখ্যা মিলে যাচ্ছে — তাই 1 → x, 2 → y, 3 → z।

Element-সংখ্যা না মিললে error:

fn main() {
    let (x, y) = (1, 2, 3);
}
compile errortext
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

ঠিক করতে — হয় কয়েকটা element _ বা .. দিয়ে ignore করো, অথবা variable-সংখ্যা এবং element-সংখ্যা match করাও।

if let — conditional

চ্যাপ্টার ৬-এ if let দেখেছি — এক arm-এর match-এর shorthand। এতে else-ও থাকতে পারে, এবং else if / else if let দিয়ে chain করা যায়। সবগুলো condition একটাই value-এর সাথে relate করতে বাধ্য না — এটা match-এর চেয়ে flexible।

src/main.rsrust
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");
    }
}

User favorite color দিলে সেটা background; না দিলে Tuesday হলে green; না হলে age parse হলে 30-এর উপর — purple, না হলে orange; কোনোটাই না হলে blue।

লক্ষ্য করো — else if let Ok(age) = age-এ ভিতরের age বাইরের age-কে shadow করছে; তাই if age > 30 সেই block-এর ভিতরে। বাইরের scope-এ valid না।

Disadvantage — exhaustiveness check হয় না। match-এ compiler সব case cover হচ্ছে কিনা ধরে; if let chain-এ একটা case ভুলে গেলে compiler বলবে না।

while let — conditional loop

if let-এর কাজিন — যতক্ষণ pattern match হচ্ছে loop চলে।

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}");
    }
}

এই code 1, 2, 3 print করবে। recv message নিয়ে Ok(value) দেয়; sender disconnect হলে Err — pattern match fail, loop শেষ।

for loop

for-এর পরের অংশ একটা pattern। for x in y-এ x pattern। Tuple destructure-এ এটা স্পষ্ট:

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

    for (index, value) in v.iter().enumerate() {
        println!("{value} is at index {index}");
    }
}
a is at index 0
b is at index 1
c is at index 2

enumerate — index ও value-এর tuple দেয়; সেটা pattern (index, value)-এ destructure।

Function parameter

Function-এর parameter-ও pattern।

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

fn main() {}

এখানে x একটা pattern (সবচেয়ে সরল form)। আবার tuple destructure-ও parameter-এ করা যায়:

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

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
}

&(3, 5) match হয় pattern &(x, y)-এর সাথে — x = 3, y = 5।

Closure-এর parameter list-ও same — closure function-এর কাছাকাছি, আগের অধ্যায়ে দেখেছি।

Refutable vs irrefutable

সব pattern সব জায়গায় use করা যায় না। কোথাও pattern-কে fail হওয়া চলবে না (irrefutable), কোথাও fail করা যাবে (refutable)। পরের পাঠে এই দু'টো concept।

এই পাঠ থেকে যা শিখলে

  • Pattern Rust-এর অনেক জায়গায় — match, if let, while let, for, let, function parameter।
  • let x = 5; এমনকি একটা pattern (সরল form)।
  • Tuple destructure — let (x, y, z) = ...fn f(&(x, y): &(i32, i32))
  • if let chain flexible, কিন্তু exhaustiveness check নেই।
  • while let — pattern fail না করা পর্যন্ত loop।