rust - 使用函数样式(flat_map等)实现递归迭代器的麻烦,该函数样式可处理错误并产生Result <…>类型的项

标签 rust iterator type-inference

作为一项学习练习,我试图编写一个使用函数样式(使用flat_map之类的函数组合其他迭代器和内容)在文件树上实现迭代器的函数,该函数将生成Result <...>类型的项,并使用表示错误(而不是 panic )。在其他语言(如Python和Kotlin)中,这很容易,但是我听说在Rust中要难一些,我想知道它有多难。我有一个简化的变体工作(对错误感到 panic ),但是我不知道用正确的错误处理方法来编写相同函数的方法。在下面的代码中,我应该在match的分支中产生错误,我已经尝试了所有可能的事情(对我而言),并且我始终会遇到有关match臂中不兼容类型的编译时错误。我在这里想念什么?

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)>>>`

最佳答案

在您的外部match中,编译器可以推断出由于返回类型的原因,两个分支都应被强制转换为Box<dyn Iterator>。但是,由于match在内部迭代器类型上是通用的,因此该推断不会扩展到嵌套flat_map中。
要解决此问题,您可以注释匹配的第一个分支,或创建具有预期类型的​​临时变量:

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(...),
}

下一组错误源于这样的事实,即胁迫实际上只在一个层次上发生。 Box<io::Error>可转换为Box<dyn Error>,但Result<_, Box<io::Error>>不可转换为Result<_, Box<dyn Error>>
要解决此问题,您需要主动将其转换为正确的类型:
Box::new(once(Err(Box::new(e) as Box<dyn Error>)))

这是编译版本:
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>)))
        }
    }
}

关于rust - 使用函数样式(flat_map等)实现递归迭代器的麻烦,该函数样式可处理错误并产生Result <…>类型的项,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66297608/

相关文章:

rust - 如果索引范围无效,则获取 None 而不是字符串切片

java - 推断嵌套静态泛型函数的泛型类型

c++ - 自定义 C++ 预处理器/Typeful 宏

PHP:iterator_to_array() 可以在 MongoCursor 上抛出异常吗

c# - 为什么泛型类型推断在那种情况下不起作用?

rust - 迭代递归结构时无法获取可变引用 : cannot borrow as mutable more than once at a time

rust - 为什么索引方法需要所有权?

rust - 为什么带有保护子句的匹配模式不是详尽无遗的?

java - Java 中迭代器因 1 次混淆而关闭

c# - C# 是否会受益于枚举器种类之间的区别,例如 C++ 迭代器?