Reputation: 8092
I'm parsing a text file line by line so I have a line number as context:
#[derive(Debug, Clone)]
pub struct Position {
pub line: usize,
pub column: usize,
}
#[derive(Debug)]
pub enum ParseError {
IoError(io::Error),
InvalidRecord(Position),
EncodingError(Position),
}
I have a loop like this:
let mut pos = Position { line: 0, column: 0 };
const LF: u8 = 0xa;
let mut record_buf = Vec::new();
while let Ok(nbytes) = reader.read_until(LF, &mut record_buf) {
// if record_buf contains some special bytes, then
// we have several numbers in ASCII
let x = str::from_utf8(&record_buf[42..(42 + 2)])?.parse::<u32>()?;
let y = str::from_utf8(&record_buf[46..(46 + 4)])?.parse::<u32>()?;
//at the end
record_buf.clear();
pos.line += 1;
}
I want to automate mapping Utf8Error
to ParseError::EncodingError
and
ParseIntError
to ParseError::EncodingError
.
I can not just implement impl From<Utf8Error> for ParseError
,
because the context in the form of line number is not available in the trait implementation.
How can I simplify my coding and not write verbose error handling like this for every number that I want extract from Vec<u8>
?
str::from_utf8(&record_buf[42..(42 + 2)])
.map_err(|_| ParseError::EncodingError(pos.clone()))?
.parse::<u32>()
.map_err(|_| ParseError::InvalidRecord(pos.clone()))?
Upvotes: 1
Views: 571
Reputation: 430861
TL;DR: Use a crate like quick_error, error-chain, or failure.
I can not just implement
impl From<Utf8Error> for ParseError
, because the context in the form of line number is not available in the trait implementation.
That's true, but that doesn't mean you can't produce a type that carries the context.
You can simplify your call site down to something like this:
let val = str::from_utf8(&record_buf[4..][..2])
.context(pos)?
.parse()
.context(pos)?;
To do so, we create a new type to hold our combination context and original error, then implement an extension trait for Result
to add the context to an error:
struct Context<V, E>(V, E);
trait ContextExt<T, E> {
fn context<V>(self, v: V) -> Result<T, Context<V, E>>;
}
impl<T, E> ContextExt<T, E> for Result<T, E> {
fn context<V>(self, v: V) -> Result<T, Context<V, E>> {
self.map_err(|e| Context(v, e))
}
}
We then implement From<Context<...>> for Error
for each interesting thing:
impl From<Context<Position, str::Utf8Error>> for ParseError {
fn from(other: Context<Position, str::Utf8Error>) -> ParseError {
ParseError::EncodingError(other.0, other.1)
}
}
impl From<Context<Position, num::ParseIntError>> for ParseError {
fn from(other: Context<Position, num::ParseIntError>) -> ParseError {
ParseError::InvalidRecord(other.0, other.1)
}
}
The last ergonomic change is to implement Copy
for your Postion
type, which makes it much easier to use — no more calls to .clone()
.
The aforementioned crates make this way easier.
Here's all the code with quick-error (my favorite):
#[macro_use]
extern crate quick_error;
use quick_error::ResultExt;
use std::{num, str};
#[derive(Debug, Copy, Clone)]
pub struct Position {
pub line: usize,
pub column: usize,
}
quick_error! {
#[derive(Debug)]
pub enum ParseError {
EncodingError(pos: Position, err: str::Utf8Error) {
context(pos: Position, err: str::Utf8Error) -> (pos, err)
}
InvalidRecord(pos: Position, err: num::ParseIntError) {
context(pos: Position, err: num::ParseIntError) -> (pos, err)
}
}
}
fn inner_main() -> Result<u32, ParseError> {
let record_buf = b"kode12abc";
let pos = Position { line: 1, column: 2 };
let val = str::from_utf8(&record_buf[4..][..2])
.context(pos)?
.parse()
.context(pos)?;
Ok(val)
}
fn main() {
let v = inner_main().expect("boom");
println!("{}", v)
}
Upvotes: 2