如何减少 std::io::Chain

dun*_*cks 3 rust

https://doc.rust-lang.org/rust-by-example/std_misc/file/read_lines.html 继续,我想定义一个接受路径可迭代的函数,并返回一个包装所有进入单个流的路径,我的不可编译的尝试,

fn read_lines<P, I: IntoIterator<Item = P>>(files: I) -> Result<io::Lines<io::BufReader<File>>>
where
    P: AsRef<Path>,
{
    let handles = files.into_iter()
    .map(|path| 
             File::open(path).unwrap());

    // I guess it is hard (impossible?) to define the type of this reduction,
    //    Chain<File, Chain<File, ..., Chain<File, File>>>
    // and that is the reason the compiler is complaining.
    match handles.reduce(|a, b| a.chain(b)) {
    Some(combination) => Ok(BufReader::new(combination).lines()),
    None => {
        // Not nice, hard fail if the array len is 0
        Ok(BufReader::new(handles.next().unwrap()).lines())
    },
    }
}
Run Code Online (Sandbox Code Playgroud)

这给出了一个预期的错误,我不确定如何解决,

error[E0599]: the method `chain` exists for struct `File`, but its trait bounds were not satisfied
   --> src/bin.rs:136:35
    |
136 |     match handles.reduce(|a, b| a.chain(b)) {
    |                                   ^^^^^ method cannot be called on `File` due to unsatisfied trait bounds
    | 
   ::: /home/test/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/fs.rs:91:1
    |
91  | pub struct File {
    | --------------- doesn't satisfy `File: Iterator`
    | 
   ::: /home/test/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/mod.rs:902:8
    |
902 |     fn chain<R: Read>(self, next: R) -> Chain<Self, R>
    |        ----- the method is available for `Box<File>` here
    |
    = note: the following trait bounds were not satisfied:
            `File: Iterator`
            which is required by `&mut File: Iterator`
    = help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
    |
1   | use std::io::Read;
    |

error: aborting due to previous error
Run Code Online (Sandbox Code Playgroud)

我试过用Box's扭曲代码没有成功,但似乎根本问题是这种减少的类型是“未定义的”:Chain<File, Chain<File, ..., Chain<File, File>>>IIUC。Rustacean 如何定义这样的方法?是否可以不使用动态“盒子”?

use*_*342 8

我想很难(不可能?)定义这种减少的类型,Chain<File, Chain<File, ..., Chain<File, File>>>. [...] Rustacean 如何定义这样的方法?

您正在寻找的组合器是flat_map

let handles = files.into_iter().map(|path| File::open(path).unwrap());
handles.flat_map(|handle| BufReader::new(handle).lines())
Run Code Online (Sandbox Code Playgroud)

此外,您的返回类型是不必要的特定,提交到句柄上的迭代器和来自句柄的行上的迭代器的特定实现。即使你让它工作,你的函数的签名将与其实现紧密耦合,这意味着你将无法在不引入 API 重大更改的情况下切换到更有效的方法。

为了避免这种耦合,您可以使用impl Trait返回类型。这样,您的函数签名仅承诺返回值的类型将实现Iterator. 该函数可能如下所示:

fn read_lines<P, I: IntoIterator<Item = P>>(files: I) -> impl Iterator<Item = io::Result<String>>
where
    P: AsRef<Path>,
{
    let handles = files.into_iter().map(|path| File::open(path).unwrap());
    handles.flat_map(|handle| BufReader::new(handle).lines())
}
Run Code Online (Sandbox Code Playgroud)

最后,如果你真的想结合reduceand chain,你也可以这样做。你的直觉,你需要使用一个Box是正确的,但它是非常容易使用fold()reduce()

handles.fold(
    Box::new(std::iter::empty()) as Box<dyn Iterator<Item = _>>,
    |iter, handle| Box::new(iter.chain(BufReader::new(handle).lines())),
)
Run Code Online (Sandbox Code Playgroud)

折叠从一个空的迭代器开始,装箱并转换为一个 trait 对象,然后将每一行链接handle到前一个迭代器链的末尾。链的每个结果都被装箱,以便将其类型擦除为Box<dyn Iterator<Item = io::Result<String>>>,从而消除了类型级别的递归。函数的返回类型可以是impl Iteratoror Box<dyn Iterator>,两者都会编译。

请注意,此解决方案效率低下,不仅是因为装箱,还因为最终迭代器将包装所有先前的迭代器。尽管从擦除的类型中看不到递归,但它在实现中存在,并且 finalnext()在内部必须遍历所有堆叠的迭代器,如果有足够数量的files. 基于的解决方案flat_map()没有这个问题。

  • 非常感谢你这么好的回答。除了回答我的问题之外,我还从中学到了很多东西。 (2认同)