如何自动向Err添加上下文?

问题描述 投票:1回答:1

我正在逐行解析文本文件,所以我有一个行号作为上下文:

#[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::EncodingErrorParseIntErrorParseError::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()))? 
rust
1个回答
2
投票

TL; DR:使用像quick_errorerror-chainfailure这样的箱子。


我不能只实现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()

Playground


上述板条箱使这种方式更容易。

这是所有带快速错误的代码(我最喜欢的):

#[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)
}
© www.soinside.com 2019 - 2024. All rights reserved.