错误处理

main 中的正确处理错误

error-chain-badge cat-rust-patterns-badge

要处理尝试打开不存在的文件时,发生的错误。我们会用到error-chain,这是一个大量样板代码的库,用来[处理 Rust 的错误]。

foreign_links里面的Io(std::io::Error),允许从std::io::Errorerror_chain!所定义类型的自动转换,这些类型会实现Error trait。

下面的食谱是,在打开 unix 文件/proc/uptime,然后分析内容得到第一个数字期间,告诉我们系统运行了多长时间。要返回 uptime,除非出现错误。

本书中的其他食谱,会隐藏error-chain样板文件,可以通过按钮(展开代码)来查看。

#[macro_use]
extern crate error_chain;

use std::fs::File;
use std::io::Read;

error_chain!{
    foreign_links {
        Io(std::io::Error);
        ParseInt(::std::num::ParseIntError);
    }
}

fn read_uptime() -> Result<u64> {
    let mut uptime = String::new();
    File::open("/proc/uptime")?.read_to_string(&mut uptime)?;

    Ok(uptime
        .split('.')
        .next()
        .ok_or("Cannot parse uptime data")?
        .parse()?)
}

fn main() {
    match read_uptime() {
        Ok(uptime) => println!("uptime: {} seconds", uptime),
        Err(err) => eprintln!("error: {}", err),
    };
}

避免在错误转换期间,丢掉了错误

error-chain-badge cat-rust-patterns-badge

这个error-chain箱子,能匹配函数返回的不同错误类型,可能是相对紧凑的。ErrorKind能确定错误类型。

使用reqwest查询一个随机整数生成器的 Web 服务。将响应的字符串,转换为整数。我们有 Rust 标准库,reqwest,并且 Web 服务的全部错误都会(可能)发生。要明确定义的 Rust 错误,请使用foreign_links。 对于额外的 Web 服务错误ErrorKind变种,使用error_chain!宏的errors代码块。

#[macro_use]
extern crate error_chain;
extern crate reqwest;

use std::io::Read;

error_chain! {
    foreign_links {
        Io(std::io::Error);
        Reqwest(reqwest::Error);
        ParseIntError(std::num::ParseIntError);
    }

    errors { RandomResponseError(t: String) }
}

fn parse_response(mut response: reqwest::Response) -> Result<u32> {
    let mut body = String::new();
    response.read_to_string(&mut body)?;
    body.pop();
    body.parse::<u32>()
        .chain_err(|| ErrorKind::RandomResponseError(body))
}

fn run() -> Result<()> {
    let url =
        format!("https://www.random.org/integers/?num=1&min=0&max=10&col=1&base=10&format=plain");
    let response = reqwest::get(&url)?;
    let random_value: u32 = parse_response(response)?;

    println!("a random number between 0 and 10: {}", random_value);

    Ok(())
}

fn main() {
    if let Err(error) = run() {
        match *error.kind() {
            ErrorKind::Io(_) => println!("Standard IO error: {:?}", error),
            ErrorKind::Reqwest(_) => println!("Reqwest error: {:?}", error),
            ErrorKind::ParseIntError(_) => println!("Standard parse int error: {:?}", error),
            ErrorKind::RandomResponseError(_) => println!("User defined error: {:?}", error),
            _ => println!("Other error: {:?}", error),
        }
    }
}

获取复杂的错误场景的回溯

error-chain-badge cat-rust-patterns-badge

这个食谱,演示了如何处理复杂的错误场景,然后打印回溯。它依赖chain_err附加新错误,来扩展错误(信息)。错误(信息)栈可以展开,从而提供更好的上下文,来理解引发错误的原因。

以下食谱,尝试将值256反序列化成一个u8。一个错误从 serde 开始冒泡,然后是 csv,最后到达,用户代码。

# extern crate csv;
#[macro_use]
extern crate error_chain;
# #[macro_use]
# extern crate serde_derive;
#
# use std::fmt;
#
# error_chain! {
#     foreign_links {
#         Reader(csv::Error);
#     }
# }

#[derive(Debug, Deserialize)]
struct Rgb {
    red: u8,
    blue: u8,
    green: u8,
}

impl Rgb {
    fn from_reader(csv_data: &[u8]) -> Result<Rgb> {
        let color: Rgb = csv::Reader::from_reader(csv_data)
            .deserialize()
            .nth(0)
            .ok_or("Cannot deserialize the first CSV record")?
            .chain_err(|| "Cannot deserialize RGB color")?;

        Ok(color)
    }
}

# impl fmt::UpperHex for Rgb {
#     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#         let hexa = u32::from(self.red) << 16 | u32::from(self.blue) << 8 | u32::from(self.green);
#         write!(f, "{:X}", hexa)
#     }
# }
#
fn run() -> Result<()> {
    let csv = "red,blue,green
102,256,204";

    let rgb = Rgb::from_reader(csv.as_bytes()).chain_err(|| "Cannot read CSV data")?;
    println!("{:?} to hexadecimal #{:X}", rgb, rgb);

    Ok(())
}

fn main() {
    if let Err(ref errors) = run() {
        eprintln!("Error level - description");
        errors
            .iter()
            .enumerate()
            .for_each(|(index, error)| eprintln!("└> {} - {}", index, error));

        if let Some(backtrace) = errors.backtrace() {
            eprintln!("{:?}", backtrace);
        }
#
#         // In a real use case, errors should handled. For example:
#         // ::std::process::exit(1);
    }
}

已呈现的错误回溯:

Error level - description
└> 0 - Cannot read CSV data
└> 1 - Cannot deserialize RGB color
└> 2 - CSV deserialize error: record 1 (line: 2, byte: 15): field 1: number too large to fit in target type
└> 3 - field 1: number too large to fit in target type

使用RUST_BACKTRACE=1,显示与此错误关联的backtrace的详细信息。