Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filtering files or directories discovered with fs::read_dir()

I've got this function:

fn folders(dir: &Path) -> Result<Vec<PathBuf>, io::Error> {
    fs::read_dir(dir)?
        .into_iter()
        .map(|x| x.map(|entry| entry.path()))
        .collect()
}

It's actually borrowed from here. The function is OK; unfortunately, I don't really understand how it works.

Ok(["/home/ey/dir-src/9", "/home/ey/dir-src/11", "/home/ey/dir-src/03 A Letter of Explanation.mp3", "/home/ey/dir-src/02 Egyptian Avenue.mp3", "/home/ey/dir-src/alfa", "/home/ey/dir-src/10"])

The test output shows both directories and files, just as it should. I can't figure out where to put filtering for files/directories. I don't understand why the mapping inside mapping: isn't it just a simple list of paths? What is really happening inside this expression?

UPD:

fn folders(dir: &Path) -> Result<Vec<PathBuf>, io::Error> {
    fs::read_dir(dir)?
        .into_iter()
        .map(|x| x.map(|entry| entry.path()))
        .filter(|x| {x.as_ref().map(|entry| entry); true})
        .collect()
}

A trivial filter (always true) inserted. It is compiling at least, but I still can't see how am I supposed to use entry for file/directory checking. Sorry :)

like image 894
Alexey Orlov Avatar asked Oct 22 '25 19:10

Alexey Orlov


1 Answers

Let's walk step by step through the chain.

fs::read_dir(dir)?

creates a read handle to the directory, immediately propagates the Err case if it happens, and if it does not, unwraps the success (that's the ? operator)

.into_iter()

turns this read handle into an iterator of Result<DirEntry>

.map(|x|
  x.map(|entry| entry.path())
)

This calls the path() method on every element of the iterator if the result is an actual DirEntry. Because the iterator element is Result<DirEntry> and not just DirEntry, the second map() allows you to deal with this cleanly. You're left with the paths you see on output

.collect()

turns this iterator back into a structure defined by the type hints (here, a vector)

The filtering part can be implemented before or after the call to map() to turn the entry into a PathBuf. If you need to filter based on the element itself and not the PathBuf, filter before it. If you can filter based on the PathBuf, filter after it.

The use of the filter() combinator function is straightforward - you give it a closure, it will apply it to every element. If the return of the closure is true, the element is kept. If it is false, the element is dropped.

Here is an example, to only return directories:

fn folders(dir: &Path) -> Result<Vec<PathBuf>, io::Error> {
    Ok(fs::read_dir(dir)?
        .into_iter()
        .filter(|r| r.is_ok()) // Get rid of Err variants for Result<DirEntry>
        .map(|r| r.unwrap().path()) // This is safe, since we only have the Ok variants
        .filter(|r| r.is_dir()) // Filter out non-folders
        .collect())
}
like image 56
Sébastien Renauld Avatar answered Oct 24 '25 14:10

Sébastien Renauld



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!