Flat_map chaining

Like many of you, I suspect, I have grown tired of the warning which displays each time it runs:

/usr/bin/which: this version of `which' is deprecated; use `command -v' in scripts instead.

I use a non-standard shell and don't have command -v :frowning:

Regardless, I decided to write my own implementation. It's just searching a bunch of dirs, should be easy. I think there's something very elegant about iterator chains, and I'm pretty happy with my result.

extern crate regex;

use regex::Regex;
use std::{env, convert::identity};

fn main() {
    // Each command line argument to a regex
    let patterns: Vec<Regex> = env::args().skip(1)
        .map(|arg| Regex::new(&arg).expect("Invalid regex"))
        .collect();

    if patterns.is_empty() {
        println!("Error, no command line arguments provided");
        return;
    }

    env::var("PATH").expect("Couldn't load path")
        .split(":")
        .flat_map(|p| std::fs::read_dir(p)) // Result<ReadDir> -> ReadDir
        .flat_map(identity)                 // ReadDir -> Result<DirEntry>
        .flat_map(identity)                 // DirEntry
        .map(|p| p.path())                  // Path
        .filter(|p| !p.is_dir())            // Path (that aren't directories)
        .map(|p| p.into_os_string().into_string().expect("non utf-8 path")) // Paths as string
        .filter(|path| patterns.iter().all(|pattern| pattern.is_match(path))) // Matching strings
        .for_each(|p| println!("{}", p));
}

My one point of friction is the triple flat_map in my iterator chain. Is there a way to implement this in a single map? The flattening + double unwraps are weird, and best I could think of is:

.flat_map(|p| {
    match std::fs::read_dir(p) {
        Err(_) => None,
        Ok(entries) => Some(entries.iter().flat_map(identity)),
    }
})

The above doesn't work. Either flat_map maps Option<T> -> T, or Iterator<Item = T> -> T. Is there a way to do both? Is there a different way to get the same effective behavior? Are there any general rust-isms I'm missing in my example?

If you're fine with methods on the outer iterator, you could use .flat_map(|p| std::fs::read_dir(p)).flatten().flatten(), since Iterator::flatten() is equivalent to flat_map(identity). Alternatively, you could use Result::into_iter() to write .flat_map(|p| std::fs::read_dir(p).into_iter().flatten().flatten()). In general, there's no way to do multiple flattens with a single function call.

2 Likes