Reputation: 8128
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
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