Display Name
Display Name

Reputation: 8128

Troubles implementing a recursive iterator using functional style (flat_map, etc) which handles errors and produces items of type Result<...>

As a learning exercise, I'm trying to write a function that implements an iterator over a file tree, using functional style (compose other iterators and stuff using functions like flat_map) which yields items of type Result<...>, and uses that to signal errors (instead of panicking). This is something that's easy in other languages like Python and Kotlin, but I've heard it's harder in Rust and I wanted to find out how much harder is it. I've got a simplified variant working (which panics on errors), but I can't figure out the way to write the same function with correct handling of errors. In the code below, in the branches of match where I should produce errors, I've tried everything imaginable (for me) and I always get compile time errors about incompatible types in match arms. What do I miss here?

use std::path::{Path, PathBuf};
use std::fs;
use std::iter::empty;
use std::iter::once;
use std::error::Error;

type Result<T> = std::result::Result<T, Box<dyn Error>>;

// simpler version which works, but panics on errors
pub fn subdirectories_recursive_panicking(search_root_path: &Path) -> Box<dyn Iterator<Item=PathBuf>> {
    if !search_root_path.is_dir() {
        return Box::new(empty());
    }

    let entries = fs::read_dir(search_root_path).unwrap();
    Box::new(entries.flat_map(|entry| {
        let this_path = entry.unwrap().path();
        let nested_paths = subdirectories_recursive_panicking(&this_path);
        nested_paths.chain(once(this_path))
    }))
}

// below is the unlucky attempt to make it correctly handle errors
// (can't figure out why I get incompatible types in match arms)
// it works, however, if I put `unreachable!()` in place of error expressions, suggesting that the
// other code makes sense, but that would obviously panic on errors
pub fn subdirectories_recursive(search_root_path: &Path) -> Box<dyn Iterator<Item=Result<PathBuf>>> {
    if !search_root_path.is_dir() {
        return Box::new(empty());
    }

    let entries_result = fs::read_dir(search_root_path);
    match entries_result {
        Ok(entries) => {
            Box::new(entries.flat_map(|e| {
                match e {
                    Ok(entry) => {
                        let this_path = entry.path();
                        let nested_paths = subdirectories_recursive(&this_path);
                        Box::new(nested_paths.chain(once(Ok(this_path))))
                    }
                    Err(e) => {
                        unreachable!()
                        // the following doesn't compile:
                        // Box::new(once(Err(Box::new(e))))
                    }
                }
            }))
        }
        Err(e) => {
            unreachable!()
            // the following doesn't compile:
            // Box::new(once(Err(Box::new(e))))
        }
    }
}
error[E0308]: `match` arms have incompatible types
  --> src/lib.rs:44:25
   |
36 | /                 match e {
37 | |                     Ok(entry) => {
38 | |                         let this_path = entry.path();
39 | |                         let nested_paths = subdirectories_recursive(&this_path);
40 | |                         Box::new(nested_paths.chain(once(Ok(this_path))))
   | |                         ------------------------------------------------- this is found to be of type `Box<std::iter::Chain<Box<dyn Iterator<Item = std::result::Result<PathBuf, Box<(dyn std::error::Error + 'static)>>>>, std::iter::Once<std::result::Result<PathBuf, Box<(dyn std::error::Error + 'static)>>>>>`
...  |
44 | |                         Box::new(once(Err(Box::new(e))))
   | |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `std::iter::Chain`, found struct `std::iter::Once`
45 | |                     }
46 | |                 }
   | |_________________- `match` arms have incompatible types
   |
   = note: expected type `Box<std::iter::Chain<Box<dyn Iterator<Item = std::result::Result<PathBuf, Box<(dyn std::error::Error + 'static)>>>>, std::iter::Once<std::result::Result<PathBuf, Box<(dyn std::error::Error + 'static)>>>>>`
            found struct `Box<std::iter::Once<std::result::Result<_, Box<std::io::Error>>>>`

error[E0271]: type mismatch resolving `<std::iter::Once<std::result::Result<_, Box<std::io::Error>>> as Iterator>::Item == std::result::Result<PathBuf, Box<(dyn std::error::Error + 'static)>>`
  --> src/lib.rs:51:13
   |
51 |             Box::new(once(Err(Box::new(e))))
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected trait object `dyn std::error::Error`, found struct `std::io::Error`
   |
   = note: expected enum `std::result::Result<PathBuf, Box<(dyn std::error::Error + 'static)>>`
              found type `std::result::Result<_, Box<std::io::Error>>`
   = note: required for the cast to the object type `dyn Iterator<Item = std::result::Result<PathBuf, Box<(dyn std::error::Error + 'static)>>>`

Upvotes: 0

Views: 218

Answers (1)

kmdreko
kmdreko

Reputation: 60072

In your outer match, the compiler can infer that both branches should be coerced to Box<dyn Iterator> because of the return type. But that inference doesn't bubble up into the nested match since flat_map is generic over the inner iterator type.

To fix that, you can either annotate the first branch of the match, or create a temporary variable with the expected type:

match e {
    Ok(_) => Box::new(...) as Box<dyn Iterator<Item = Result<PathBuf>>>,
    Err(_) => Box::new(...),
}
// or
let iter: Box<dyn Iterator<Item = Result<PathBuf>>> = match e {
    Ok(_) => Box::new(...),
    Err(_) => Box::new(...),
}

The next set of errors stem from the fact that coercion only really happens one level deep. Box<io::Error> is convertible to Box<dyn Error> but Result<_, Box<io::Error>> is not convertible to Result<_, Box<dyn Error>>.

To fix that, you need to proactively convert it into the correct type:

Box::new(once(Err(Box::new(e) as Box<dyn Error>)))

Here is the compiling version:

use std::path::{Path, PathBuf};
use std::fs;
use std::iter::empty;
use std::iter::once;
use std::error::Error;

type Result<T> = std::result::Result<T, Box<dyn Error>>;

// simpler version which works, but panics on errors
pub fn subdirectories_recursive_panicking(search_root_path: &Path) -> Box<dyn Iterator<Item=PathBuf>> {
    if !search_root_path.is_dir() {
        return Box::new(empty());
    }

    let entries = fs::read_dir(search_root_path).unwrap();
    Box::new(entries.flat_map(|entry| {
        let this_path = entry.unwrap().path();
        let nested_paths = subdirectories_recursive_panicking(&this_path);
        nested_paths.chain(once(this_path))
    }))
}

// below is the unlucky attempt to make it correctly handle errors
// (can't figure out why I get incompatible types in match arms)
// it works, however, if I put `unreachable!()` in place of error expressions, suggesting that the
// other code makes sense, but that would obviously panic on errors
pub fn subdirectories_recursive(search_root_path: &Path) -> Box<dyn Iterator<Item=Result<PathBuf>>> {
    if !search_root_path.is_dir() {
        return Box::new(empty());
    }

    let entries_result = fs::read_dir(search_root_path);
    match entries_result {
        Ok(entries) => {
            Box::new(entries.flat_map(|e| {
                let iter: Box<dyn Iterator<Item = Result<PathBuf>>> = match e {
                    Ok(entry) => {
                        let this_path = entry.path();
                        let nested_paths = subdirectories_recursive(&this_path);
                        Box::new(nested_paths.chain(once(Ok(this_path))))
                    }
                    Err(e) => {
                        // the following doesn't compile:
                        Box::new(once(Err(Box::new(e) as Box<dyn Error>)))
                    }
                };
                iter
            }))
        }
        Err(e) => {
            // the following doesn't compile:
            Box::new(once(Err(Box::new(e) as Box<dyn Error>)))
        }
    }
}

Upvotes: 1

Related Questions