从 https://doc.rust-lang.org/rust-by-example/std_misc/file/read_lines.html 开始,我想定义一个函数,它接受一个可迭代的路径,并返回一个将所有路径包装到单个流中的 Reader,我的不可编译尝试,
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())
},
}
}
这给出了一个预期的错误,我不确定如何解决,
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
我尝试用 Box
扭曲代码没有成功,但似乎根本问题是这种减少的类型是“未定义的”:Chain<File, Chain<File, ..., Chain<File, File>>>
国际大学联合会。 Rustacean 如何定义这样的方法?是否可以不使用动态“框”?
最佳答案
I guess it is hard (impossible?) to define the type of this reduction,
Chain<File, Chain<File, ..., Chain<File, File>>>
. [...] How would a Rustacean define a method like this?
您要查找的组合子是 flat_map
:
let handles = files.into_iter().map(|path| File::open(path).unwrap());
handles.flat_map(|handle| BufReader::new(handle).lines())
此外,您的返回类型是不必要的特定, promise 对句柄上的迭代器和来自句柄的行上的迭代器的特定实现。即使你让它工作,你的函数的签名将与它的实现紧密耦合,这意味着你将无法例如在不对 API 进行重大更改的情况下切换到更高效的方法。
为避免这种耦合,您可以使用 impl Trait
返回类型。这样你的函数的签名只 promise 返回值的类型将实现 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())
}
最后,如果你真的想合并reduce
和 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())),
)
折叠从一个空的迭代器开始,装箱并强制转换为一个特征对象,然后是每个 handle
的链线。到前一个迭代器链的末尾。链的每个结果都被装箱,以便其类型被删除为 Box<dyn Iterator<Item = io::Result<String>>>
,这消除了类型级别的递归。函数的返回类型可以是 impl Iterator
或 Box<dyn Iterator>
,两者都会编译。
请注意,这种解决方案效率低下,不仅是因为装箱,还因为最终迭代器将包装所有以前的迭代器。虽然从删除的类型中看不到递归,但它存在于实现中,最终的 next()
内部必须遍历所有堆叠的迭代器,如果有足够数量的files
,甚至可能会炸毁堆栈。 .基于flat_map()
的解决方案没有这个问题。
关于rust - 如何减少 std::io::Chain,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67382232/