我正在逐行解析文本文件,所以我有一个行号作为上下文:
#[derive(Debug, Clone)]
pub struct Position {
pub line: usize,
pub column: usize,
}
#[derive(Debug)]
pub enum ParseError {
IoError(io::Error),
InvalidRecord(Position),
EncodingError(Position),
}
我有一个这样的循环:
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;
}
我想自动将Utf8Error
映射到ParseError::EncodingError
和
ParseIntError
至ParseError::EncodingError
。
我不能只实施impl From<Utf8Error> for ParseError
,
因为特征实现中没有行号形式的上下文。
我如何简化我的编码,而不是为Vec<u8>
提取的每个数字编写这样的详细错误处理?
str::from_utf8(&record_buf[42..(42 + 2)])
.map_err(|_| ParseError::EncodingError(pos.clone()))?
.parse::<u32>()
.map_err(|_| ParseError::InvalidRecord(pos.clone()))?
答案 0 :(得分:2)
TL; DR:使用quick_error,error-chain或failure这样的包。
我不能只实现
impl From<Utf8Error> for ParseError
,因为特征实现中没有行号形式的上下文。
这是真的,但这并不意味着你不能产生一个带有上下文的类型。
您可以将呼叫网站简化为以下内容:
let val = str::from_utf8(&record_buf[4..][..2])
.context(pos)?
.parse()
.context(pos)?;
为此,我们创建一个新类型来保存我们的组合上下文和原始错误,然后为Result
实现扩展特征以将上下文添加到错误中:
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))
}
}
然后我们为每个有趣的事情实施From<Context<...>> for Error
:
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)
}
}
最后一次符合人体工程学的更改是为Copy
类型实施Postion
,这样可以更轻松地使用 - 不再需要.clone()
。
前面提到的板条箱使更容易。
这是所有带快速错误的代码(我最喜欢的):
#[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)
}