How to manually parse a file line by line?

TL; DR How do you convert None into anyhow::Error?

I have a file that contains a few values. Each line line contains (separated by whitespace)

  • a usize index
  • a f32 f
  • an i64 d

I am using anyhow to make it easy to yield errors. I would like to put everything in a HashMap, indexed by the index. The following code works:

pub struct MyData {
    pub f: f32,
    pub d: i64,
}
let file = BufReader::new(File::open(&path)?);
let mut line_number = 0;

while let Some(Ok(line)) = lines.next() {
    line_number += 1;
    let err_msg = || format!("invalid file format [{}:{}]: {}", &path.display(), line_number, &line);

    let mut token = line.split_whitespace();
    let id = token.next().with_context(err_msg)?.parse().with_context(err_msg)?;
    let f = token.next().with_context(err_msg)?.parse().with_context(err_msg)?;
    let d = token.next().with_context(err_msg)?.parse().with_context(err_msg)?;

    edges.insert(id, MyData {f, d});
}

As you can see, the code to parse the line is repetitive. Since the type of the parsed value isn't the same each time, I cannot create a lambda (Rust doesn't will use the same return type for each call), so I tried to write a function:

fn parse<'a, T, U>(token: &mut 'a T) -> Result<U>
where T: std::iter::Iterator<Item = &'a str>
{   
    Ok(token
        .next()?
        .parse()?)
}   

let id = parse(&mut token).with_context(err_msg)?;
let f = parse(&mut token).with_context(err_msg)?;
let d = parse(&mut token).with_context(err_msg)?;

But I get the following error

  4 || 100 |                 .next()?
  5 ||     |                        ^ the trait `std::error::Error` is not implemented for `std::option::NoneError`
  6 ||     |

It I remove all error handling, and just unwrap() things, it should works, but I would like to nicely report error to my caller. If I understand correctly the issue, I cannot use the try operator to yield an error if the iterator returns None when next() is called.

--

Note: if it's possible to automatically parse the line is a much simpler way, it would be even better. I don't think that Serde can be used here, but if I'm wrong, please correct me.

Sounds like you're looking for Option.ok_or or Option.ok_or_else which can convert None values to Results with errors.