1. 处理错误变量

  需要安装error-chain库,可通过cargo add error-chain 命令安装

[dependencies]
error-chain = "0.12.4"

1.1 在 main 方法中对错误适当处理

  处理尝试打开不存在的文件时发生的错误,是通过使用 error-chain crate 来实现的。error-chain crate 包含大量的模板代码,用于 Rust 中的错误处理。foreign_links 代码块内的 Io(std::io::Error) 函数允许由 std::io::Error 所报错误信息到 error_chain! 所定义错误类型的自动转换,error_chain! 所定义错误类型将实现 Error trait。下文的实例将通过打开 Unix 文件 /proc/uptime 并解析内容以获得其中第一个数字,从而告诉系统运行了多长时间。除非出现错误,否则返回正常运行时间。

use error_chain::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("无法解析正常运行时间数据")?
.parse()?)
}

fn main() {
match read_uptime() {
Ok(uptime) => println!("正常运行时间: {} 秒", uptime),
Err(err) => eprintln!("错误: {}", err),
};
}
  • 运行cargo run输出
正常运行时间: 3204645 秒

1.2 避免在错误转变过程中遗漏错误

  需要安装reqwest库,可通过cargo add reqwest --features blocking 命令安装

[dependencies]
error-chain = "0.12.4"
reqwest = { version = "0.11.16", features = ["blocking"] }

  error-chain crate 使得匹配函数返回的不同错误类型成为可能,并且相对简洁。ErrorKind 是枚举类型,可以确定错误类型。下文实例使用 reqwest::blocking 来查询一个随机整数生成器的 web 服务,并将服务器响应的字符串转换为整数。Rust 标准库 reqwest 和 web 服务都可能会产生错误,所以使用 foreign_links 定义易于辨认理解的 Rust 错误。另外,用于 web 服务错误信息的 ErrorKind 变量,使用 error_chain! 宏的 errors 代码块定义。

use error_chain::error_chain;

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

fn parse_response(response: reqwest::blocking::Response) -> Result<u32> {
let mut body = response.text()?;
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::blocking::get(&url)?;
let random_value: u32 = parse_response(response)?;
println!("0 到 10 之间的随机数: {}", random_value);
Ok(())
}

fn main() {
if let Err(error) = run() {
match *error.kind() {
ErrorKind::Io(_) => println!("标准 IO 错误: {:?}", error),
ErrorKind::Reqwest(_) => println!("reqwest 请求错误: {:?}", error),
ErrorKind::ParseIntError(_) => println!("标准解析 int 错误: {:?}", error),
ErrorKind::RandomResponseError(_) => println!("用户定义错误: {:?}", error),
_ => println!("其他错误: {:?}", error),
}
}
}
  • 运行cargo run输出
0 到 10 之间的随机数: 6

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

  需要安装csvserde库,可通过cargo add csvcargo add serde --features derive 命令安装

[dependencies]
error-chain = "0.12.4"
csv = "1.2.1"
serde = { version = "1.0.160", features = ["derive"] }

  本实例展示了如何处理一个复杂的错误场景,并且打印出错误回溯。依赖于 chain_err,通过附加新的错误来扩展错误信息。从而可以展开错误堆栈,这样提供了更好的上下文来理解错误的产生原因。下述代码尝试将值 256 反序列化为 u8。首先 Serde 产生错误,然后是 csv,最后是用户代码。

use error_chain::error_chain;
use serde::Deserialize;

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("无法反序列化第一个 CSV 记录")?
.chain_err(|| "无法反序列化 RGB 颜色")?;

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(|| "无法读取 CSV 数据")?;
println!("{:?} 十六进制 #{:X}", rgb, rgb);

Ok(())
}

fn main() {
if let Err(ref errors) = run() {
eprintln!("错误级别 - 描述");
errors
.iter()
.enumerate()
.for_each(|(index, error)| eprintln!("└> {} - {}", index, error));

if let Some(backtrace) = errors.backtrace() {
eprintln!("{:?}", backtrace);
}

//在实际用例中,应该处理错误。 例如:
std::process::exit(1);
}
}
  • 运行cargo run输出
错误级别 - 描述
└> 0 - 无法读取 CSV 数据
└> 1 - 无法反序列化 RGB 颜色
└> 2 - CSV deserialize error: record 1 (line: 2, byte: 15): field 1: number too large to fit in target type