Reputation: 1153
I'm starting with this working code:
use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader, Result};
use std::path::Path;
fn read_lines<P, I: IntoIterator<Item = P>>(files: I) -> impl Iterator<Item = 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())
}
fn main() -> Result<()> {
let lines = read_lines(env::args().skip(1).collect::<Vec<String>>());
for line in lines {
println!("{:?}", line?)
}
Ok(())
}
I need to integrate this into a codebase that is heavily reliant on the anyhow
library, but I have no idea how I can massage the BufReader::lines
return value inside the flatmap
into a impl Iterator<Item = anyhow::Result<String>>
.
As a reproducible example of where I'm stuck, I integrate anyhow
into my test bed with this Cargo.toml
,
[package]
name = "rust-playground"
version = "0.1.0"
authors = ["Charles"]
edition = "2018"
[dependencies]
anyhow = "1"
And I replace the import of std::io::Result
with anyhow::Result
. I'm unsure where to place with_context
calls, everything I've tried has led to compiler errors.
This attempt fails because I can't use ?
inside the closure, but how else can I "unwrap"? I'm not allowed to use unwrap
in this context, I'm expected to return an anyhow::Result
, somehow...
use anyhow::{Context, Result};
use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;
fn read_lines<P, I: IntoIterator<Item = P>>(files: I) -> impl Iterator<Item = Result<String>>
where
P: AsRef<Path>,
{
let handles = files.into_iter().map(|path| {
File::open(path).with_context(|| format!("opening path: {}", path.as_ref().display()))?
});
handles.flat_map(|handle| BufReader::new(handle).lines())
}
fn main() -> Result<()> {
let lines = read_lines(env::args().skip(1).collect::<Vec<String>>());
for line in lines {
println!("{:?}", line?)
}
Ok(())
}
And the error message:
error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `Try`)
--> src/main.rs:13:10
|
12 | files.into_iter().map(|path|
| ___________________________-
13 | | File::open(path).with_context(|| format!("opening path: {}", path.as_ref().display()))?);
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
| |___________________|_____________________________________________________________________________________|
| | this function should return `Result` or `Option` to accept `?`
| cannot use the `?` operator in a closure that returns `File`
|
= help: the trait `Try` is not implemented for `File`
= note: required by `from_error`
error[E0271]: type mismatch resolving `<FlatMap<Map<<I as IntoIterator>::IntoIter, [closure@src/main.rs:12:24: 13:97]>, std::io::Lines<BufReader<File>>, [closure@src/main.rs:14:22: 14:61]> as Iterator>::Item == std::result::Result<String, anyhow::Error>`
--> src/main.rs:7:58
|
7 | fn read_lines<P, I: IntoIterator<Item = P>>(files: I) -> impl Iterator<Item = Result<String>>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `std::io::Error`, found struct `anyhow::Error`
|
= note: expected enum `std::result::Result<_, std::io::Error>`
found enum `std::result::Result<_, anyhow::Error>`
I can figure out a way to make this compile if my method handles on a single filename:
fn read_lines<P>(filename: P) -> Result<io::Lines<io::BufReader<File>>>
where
P: AsRef<Path>,
{
let file = File::open(&filename)
.with_context(|| format!("opening filename: {}", filename.as_ref().display()))?;
Ok(BufReader::new(file).lines())
}
But this doesn't generalize properly to the the case of handling multiple filenames, since File::open(&path).with_context(...)?
is not correct inside the iterator.
Upvotes: 5
Views: 3703
Reputation: 1347
The last snippet you provided for handling single files doesn't seem to be returning the same thing as the previous examples.
If you want to just extend the single file example for multiple files, then you could just map the single-file function over the list of files.
Something like this
fn read_lines<P, I: IntoIterator<Item = P>>(files: I) -> impl Iterator<Item = Result<std::io::Lines<BufReader<File>>>>
where
P: AsRef<Path>,
{
files.into_iter().map(|filename: P| {
let file = File::open(&filename)
.with_context(|| format!("opening filename: {}", filename.as_ref().display()))?;
Ok(BufReader::new(file).lines())
})
}
But if you would like the function to return a Result<String>
instead of a Result<std::io::Lines<BufReader<File>>>
, you can transform the errors from io::Error
to anyhow::Error
like so,
fn read_lines<P, I: IntoIterator<Item = P>>(files: I) -> impl Iterator<Item = Result<String>>
where
P: AsRef<Path>,
{
files.into_iter().flat_map(|filename: P| {
let file = File::open(&filename)
.with_context(|| format!("opening filename: {}", filename.as_ref().display()));
file.into_iter().flat_map(|file| {
BufReader::new(file)
.lines()
.map(|line| line.map_err(anyhow::Error::from))
})
})
}
Upvotes: 4