পাঠ ১৩.৩

আমাদের I/O Project উন্নত করা

Improving Our I/O Project

Chapter 12-এর minigrep-কে iterator দিয়ে refactor করব — কম clone, কম mutable state, পরিষ্কার code।

Config::build-এ clone সরানো

আগের signature:

fn build(args: &[String]) -> Result<Config, &'static str> {
    if args.len() < 3 {
        return Err("not enough arguments");
    }

    let query = args[1].clone();
    let file_path = args[2].clone();

    let ignore_case = env::var("IGNORE_CASE").is_ok();

    Ok(Config { query, file_path, ignore_case })
}

Slice থেকে owned String পেতে .clone() করতে হচ্ছিল। Iterator নিলে এটা এড়ানো যায় — iterator owned value direct দেয়।

main-এ iterator pass

src/main.rsrust
fn main() {
    let config = Config::build(env::args()).unwrap_or_else(|err| {
        eprintln!("Problem parsing arguments: {err}");
        process::exit(1);
    });

    if let Err(e) = run(config) {
        eprintln!("Application error: {e}");
        process::exit(1);
    }
}

আগে env::args().collect() দিয়ে Vec বানিয়ে slice pass করতাম। এখন iterator সরাসরি।

Config::build নতুন form

src/lib.rsrust
impl Config {
    pub fn build(
        mut args: impl Iterator<Item = String>,
    ) -> Result<Config, &'static str> {
        args.next();

        let query = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a query string"),
        };

        let file_path = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a file path"),
        };

        let ignore_case = env::var("IGNORE_CASE").is_ok();

        Ok(Config { query, file_path, ignore_case })
    }
}
  • impl Iterator<Item = String> — যেকোনো String iterator accept।
  • mut argsnext() mutate করে।
  • প্রথম args.next() — program name skip।
  • match দিয়ে missing argument-এ proper error message।
  • Iterator owned value দিচ্ছে — clone লাগছে না!

search — iterator adapter দিয়ে

আগের loop-based version:

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut results = Vec::new();

    for line in contents.lines() {
        if line.contains(query) {
            results.push(line);
        }
    }

    results
}

Adapter chain দিয়ে — এক expression-এ:

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    contents
        .lines()
        .filter(|line| line.contains(query))
        .collect()
}

সুবিধা:

  • একটামাত্র expression — পড়তে সহজ।
  • Mutable results Vec লাগছে না।
  • Code level এখন "filter where line contains query" — implementation detail-এ মাথা ঘামাতে হচ্ছে না।

Loop নাকি iterator?

বেশিরভাগ Rust developer iterator-style প্রেফার করে — code intent-কে সরাসরি প্রকাশ করে, boilerplate বাদ। কিন্তু দুটোই functionally equivalent।

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

  • impl Iterator<Item = T> parameter type — যেকোনো iterator accept।
  • Iterator-এর next() owned value দেয় — clone লাগে না।
  • filter + collect দিয়ে loop-replace।