Error-less version of fs::read_dir?


#1

I was wondering if there is an existing idiom for an error-less read_dir? What I’m looking for is an iterator over a directory that will never give an error, but will instead give a short result. I could code this up, but if there is an example out there (and it seems like a useful iterator), I’d rather just use that. It’s a little tedious, since I will need to wrap both the “outside” and the “inside” of the read_dir, to deal with the separate issues of reading a nonexistent directory vs. trouble reading the contents of the directory.


#2

Can you explain a bit more about the problem you’re trying to solve? In particular, what is an "error-less read_dir" and why do you want it? Each iteration of ReadDir can potentially produce an error: https://github.com/rust-lang/rust/blob/master/src/libstd/sys/unix/fs.rs#L139-L141


#3

Right, I want an iterator that doesn’t produce any errors, either when it is first constructed, or when it is iterated. In case of error, I just don’t want any more results. The use case is a situation where I am wanting to know which directories (or files) exist. If they don’t exist, or cannot be read, the response is the same.


#4

Collect the iterator into a result?

let rdr = fs::read_dir("./some/path");
let dirs: io::Result<Vec<fs::DirEntry>> = rdr.collect();

It will quit after the first error and return it. But you’ll lose any entries read up until that point.


#5

Interesting, you can collect an iterator of Results into a `Result<Vec<_>>? That is cool and weird! I suppose you’ll also need to wrap this in a match or something? e.g. something more like:

let dirs: Vec<fs::DirEntry> = if let Ok(rdr) = fs::read_dir("./some/path") {
     if let Ok(v) = rdr.collect() { v } else { Vec::new() }
} else {
     Vec::new()
}

That’s definitely prettier than some of what I was fearing, although still pretty nasty, and as you say, it also loses any entries prior to an error. But then I’m not sure what would trigger an error partway through reading a directory…

Oh, but now I"m realizing what this is missing: it doesn’t result in an iterator, which means it could be very problematic in large directories. :frowning:


#6

I think your problem is still under specified. Do you care about all of the directory entries? Only the ones before an error? What about the ones after an error? I’d just write it out imperatively:

for ent in fs::read_dir("./some/path") {
    match ent {
        Ok(dir_entry) => { ... }
        Err(err) => { ... }
    }
}

#7

Well, I was hoping that I could find something that would be reusable and lazy, since I am going to be looking at the directories within the directories that I list.

The precise treatment of errors in reading is probably not important, since at least on linux readdir can only return EBADF indicating an invalid file descriptor input, which should never happen (sans bug in fs::read_dir). It’s mostly just a nuisance that I would like to avoid.

I’ve worked out a (perhaps suboptimal?) version of read_dir that is now at https://github.com/droundy/lazyfs


#8

A quick and dirty variant would be this, if I understand it correctly:

fs::read_dir("path/to/file")
    .take_while(|f| f.is_ok())
    .map(|f| f.unwrap())

or

fs::read_dir("path/to/file")
    .take_while(|f| f.is_ok())
    .filter_map(|f| f.ok())

It should only take files while they are Ok(...) and then unwrap them by just unwrapping or by filtering out the impossible cases.


#9

This kind of iterator adaptor could fit in here. It converts a Result<T, E> iterator into a T iterator that stops on first error. After iteration you can check if an error was encountered or not.

I guess if you want to ignore some errors, you’d add some kind of error-filtering feature.

pub struct WhileOk<I, E> {
    iter: I,
    error: Option<E>,
}

impl<T, E, I> WhileOk<I, E>
    where I: Iterator<Item=Result<T, E>>
{
    pub fn new<J>(iterable: J) -> Self
        where J: IntoIterator<IntoIter=I, Item=I::Item>
    {
        WhileOk {
            iter: iterable.into_iter(),
            error: None,
        }
    }

    pub fn error(&self) -> Option<&E> {
        self.error.as_ref()
    }

    pub fn take_error(&mut self) -> Option<E> {
        self.error.take()
    }

    pub fn into_error(self) -> Option<E> {
        self.error
    }
}

impl<T, E, I> Iterator for WhileOk<I, E>
    where I: Iterator<Item=Result<T, E>>
{
    type Item = T;

    fn next(&mut self) -> Option<T> {
        if self.error.is_some() {
            return None;
        }
        match self.iter.next() {
            None => None,
            Some(Ok(elt)) => Some(elt),
            Some(Err(error)) => {
                self.error = Some(error);
                None
            }
        }
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let (_, hi) = self.iter.size_hint();
        (0, hi)
    }
}