无论使用什么编程语言,错误处理都是很重要的一块。如果在生产环境中,错误没有得到妥善合理的处理,就有可能给系统埋下隐患。各种语言错误处理方式有所差异,比如 C 语言使用返回值,Go 语言在调用时返回 error,程序中充斥着if err != nil {},Java使用异常来做处理,实现了错误的产生和处理分离开,但是很容易被滥用,造成额外的开销。

Rust 走了一条完全不同的路,使用类型系统来进行错误处理。Rust 将错误分为两大类:可恢复的(recoverable)和 不可恢复的(unrecoverable)错误。Rust使用Result<T, E>类型用于处理可恢复的错误,使用panic!宏来在遇到不可恢复的错误时停止运行。但是 Rust 还有着更多的错误处理方式。

断言

Rust 标准库提供 6 个常用断言,断言失败的话就会引发程序恐慌崩溃。

  • assert!: 用于断言布尔表达式结果一定为 true
  • assert_eq!: 用于断言两个表达式一定相等
  • assert_ne!: 用于断言两个表达式一定不相等
  • debug_assert!: 等价于assert!,但是只能用于调试模式
  • debug_assert_eq!: 等价于assert_eq!,但是只能用于调试模式
  • debug_assert_ne!: 等价于assert_ne!,但是只能用于调试模式

这里面 assert 系列断言宏在 Debug 和 Release 模式下都可以使用。而debug_assert 系列断言宏只在 Debug 模式下起作用,而在 Release 模式下会忽略。由于断言有一定的性能开销,所以更建议使用 debug_assert 系列断言宏

assert!(1 == 1);
assert_eq!(1, 1);
assert_ne!(1, 2);

debug_assert!(1 == 1);
debug_assert_eq!(1, 1);
debug_assert_ne!(1, 2);

断言还支持自定义错误信息,便于开发者更清楚的发现错误并修复:

let x = false;
let a = 1;
assert!(x, "x wasn't true!");
assert_eq!(a, 2, "a = {}", a);
assert_ne!(a, 1, "a = {}", a);

debug_assert!(x, "x wasn't true!");
debug_assert_eq!(a, 2, "a = {}", a);
debug_assert_ne!(a, 1, "a = {}", a);

panic!

我们也可以使用 panic! 宏来快速制造线程恐慌,本质上 assert! 系列宏内部也是使用了 panic!宏。

let a = 1;
if a != 2 {
    panic!("a is not 2");
}

当出现 panic 时,程序默认会开始 展开(unwinding),这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择是直接 终止(abort),这会不清理数据就退出程序

那么程序所使用的内存需要由操作系统来清理。如果你需要项目的最终二进制文件越小越好。如果你想要在 release 模式中 panic 时直接终止,可以修改 Cargo.toml 文件:

[profile.release]
panic = 'abort'

Option<T> 类型处理有与无

对于有些情况,比如可能有值,也可能没有值的情况,可以使用Option<T>来进行处理。Option<T>是一个枚举体。

pub enum Option {
    None,
    Some(T),
}
  • Some<T>: 有值
  • None: 没有值或者不存在

常用方法

与值判断相关的方法:

  • is_some: 判断是否是Some
  • is_none: 判断是否为None
  • is_some_and: 如果是Some并且与闭包代码匹配,则返回 true
let x: Option<u32> = Some(2);
assert_eq!(x.is_some(), true);
assert_eq!(x.is_none(), false);
assert_eq!(x.is_some_and(|x| x > 1), true);
assert_eq!(x.is_some_and(|x| x > 2), false);

let x: Option<u32> = None;
assert_eq!(x.is_some(), false);
assert_eq!(x.is_none(), true);

与借用相关的方法:

  • as_ref: 不可变借用,将&Option<T>转换为Option<&T>
  • as_mut: 可变借用,将&mut Option<T>转换为Option<&mut T>
let text: Option<String> = Some("Hello, world!".to_string());
// 首先使用`as_ref`将`Option<String>`转换为`Option<&String>`,
// 然后使用`map`消费它
let text_length: Option<usize> = text.as_ref().map(|s| s.len());
println!("仍然可以打印 text: {text:?}");
println!("{}", text_length.unwrap());

let mut x = Some(2);
match x.as_mut() {
    Some(v) => *v = 42,
    None => {},
}
assert_eq!(x, Some(42));

取出Some内值相关的方法:

  • expect: 如果是 Some 值则返回 Some 包含的值,如果是 None 则会panic,并且自定义的恐慌信息为 msg。
  • unwrap: 返回 Some 包含的值,如果是 None 的话会 panic。
  • unwrap_or: 返回 Some 包含的值,如果是 None 的话返回一个默认值。
  • unwrap_or_else: 返回 Some 包含的值,如果是 None 时通过闭包计算返回。
  • unwrap_or_default: 返回 Some 包含的值,如果是 None 时返回 T 的默认值。
// expect方法
let x = Some("value");
assert_eq!(x.expect("fruits are healthy"), "value");
let x: Option<&str> = None;
x.expect("fruits are healthy"); // panics with `fruits are healthy`

// unwrap方法
let x = Some("air");
assert_eq!(x.unwrap(), "air");
let x: Option<&str> = None;
assert_eq!(x.unwrap(), "air"); // fails

// unwrap_or方法
assert_eq!(Some("car").unwrap_or("bike"), "car");
assert_eq!(None.unwrap_or("bike"), "bike");

// unwrap_or_else方法
let k = 10;
assert_eq!(Some(4).unwrap_or_else(|| 2 * k), 4);
assert_eq!(None.unwrap_or_else(|| 2 * k), 20);

// unwrap_or_default方法
let x: Option<u32> = None;
let y: Option<u32> = Some(12);
assert_eq!(x.unwrap_or_default(), 0);
assert_eq!(y.unwrap_or_default(), 12);

map系列方法:

  • map: 通过闭包函数,将函数应用于包含的SomeNone,并将Option<T>转换为Option<U>
  • map_or: 如果值为None就返回第一个默认值参数,如果为Some返回第二个闭包值。
  • map_or_else: 如果值为None就返回第一个闭包值,如果为Some就返回第二个闭包值。
// map方法
let maybe_some_string = Some(String::from("Hello, World!"));
let maybe_some_len = maybe_some_string.map(|s| s.len());
assert_eq!(maybe_some_len, Some(13));
let x: Option<&str> = None;
assert_eq!(x.map(|s| s.len()), None);

// map_or方法
let x = Some("foo");
assert_eq!(x.map_or(42, |v| v.len()), 3);
let x: Option<&str> = None;
assert_eq!(x.map_or(42, |v| v.len()), 42);

// map_or_else方法
let k = 21;
let x = Some("foo");
assert_eq!(x.map_or_else(|| 2 * k, |v| v.len()), 3);
let x: Option<&str> = None;
assert_eq!(x.map_or_else(|| 2 * k, |v| v.len()), 42);

转换为Result<T, E>:

  • ok_or: Some(v)会转换为Ok(v)None会转换为Err(err)
  • ok_or_else: Some(v)会转换为Ok(v)None会转换为Err(err())
// ok_or 方法
let x = Some("foo");
assert_eq!(x.ok_or(0), Ok("foo"));
let x: Option<&str> = None;
assert_eq!(x.ok_or(0), Err(0));

// ok_or_else方法
let x = Some("foo");
assert_eq!(x.ok_or_else(|| 0), Ok("foo"));
let x: Option<&str> = None;
assert_eq!(x.ok_or_else(|| 0), Err(0));

Result<T, E>类型返回错误

如果返回值可能返回错误,那么就使用Result<T, E>。它也是一个枚举类型:

pub enum Result<T, E> {
    Ok(T),
    Err(E),
}

其中:

  • Ok(T): 包含成功值
  • Err(E): 包含错误值

常用方法

判断值相关:

  • is_ok: 如果值内容为Ok就返回 true
  • is_ok_and: 如果值为Ok且闭包匹配成功,则返回 true
  • is_err: 如果值内容是Err就返回 true
  • is_err_and: 如果值为Err且闭包匹配成功,则返回 true
// is_ok与is_err方法
let x: Result<i32, &str> = Ok(-3);
assert_eq!(x.is_ok(), true);
assert_eq!(x.is_err(), false);
let x: Result<i32, &str> = Err("Some error message");
assert_eq!(x.is_ok(), false);
assert_eq!(x.is_err(), true);

// is_ok_and方法
let x: Result<u32, &str> = Ok(2);
assert_eq!(x.is_ok_and(|x| x > 1), true);
assert_eq!(x.is_ok_and(|x| x > 2), false);

// is_err_and方法
use std::io::{Error, ErrorKind};
let x: Result<u32, Error> = Err(Error::new(ErrorKind::NotFound, "!"));
assert_eq!(x.is_err_and(|x| x.kind() == ErrorKind::NotFound), true);

转换为Option类型:

  • ok: 将Result<T, E>转换为Option<T>,并且将丢弃错误
  • err: 将Result<T, E>转换为Option<E>,并且将丢弃成功值
let x: Result<u32, &str> = Ok(2);
assert_eq!(x.ok(), Some(2));
assert_eq!(x.err(), None);

let x: Result<u32, &str> = Err("Nothing here");
assert_eq!(x.ok(), None);
assert_eq!(x.err(), Some("Nothing here"));

map系列方法:

  • map: 如果是Ok则执行闭包,否则返回Err,最终返回的还是一个Result
  • map_or: 如果是Ok则执行闭包,否则返回默认值
  • map_or_else: 如果是Ok则执行第二个闭包,如果是Err则执行第一个闭包
  • map_err: 如果是Ok则返回Ok(T),如果是Err则执行闭包,返回另一个错误
// map方法
let x: Result<_, &str> = Ok("foo");
assert_eq!(x.map(|v| v.len()).unwrap(), 3);

// map_or方法
let x: Result<_, &str> = Ok("foo");
assert_eq!(x.map_or(42, |v| v.len()), 3);
let x: Result<&str, _> = Err("bar");
assert_eq!(x.map_or(42, |v| v.len()), 42);

// map_or_else方法
let k = 21;
let x : Result<_, &str> = Ok("foo");
assert_eq!(x.map_or_else(|e| k * 2, |v| v.len()), 3);
let x : Result<&str, _> = Err("bar");
assert_eq!(x.map_or_else(|e| k * 2, |v| v.len()), 42);

// map_err方法
fn stringify(x: u32) -> String { format!("error code: {x}") }
let x: Result<u32, u32> = Ok(2);
assert_eq!(x.map_err(stringify), Ok(2));
let x: Result<u32, u32> = Err(13);
assert_eq!(x.map_err(stringify), Err("error code: 13".to_string()));

unwrap系列方法:

  • unwrap: 如果是Ok(T)则返回T ,否则会 panic,panic 的信息就是 Err 的值
  • unwrap_or: 如果是Ok(T)则返回T,否则返回默认值
  • unwrap_or_else: 如果是Ok(T)则返回T,否则返回闭包值
  • unwrap_or_default: 如果是Ok(T)则返回T,否则返回类型 T 的默认值
  • unwrap_err: 如果是Err(E)则返回E,如果是Ok(T)则会 panic
// unwrap方法
let x: Result<u32, &str> = Ok(2);
assert_eq!(x.unwrap(), 2);

// unwrap_or方法
let default = 2;
let x: Result<u32, &str> = Ok(9);
assert_eq!(x.unwrap_or(default), 9);
let x: Result<u32, &str> = Err("error");
assert_eq!(x.unwrap_or(default), default);

// unwrap_or_else方法
fn count(x: &str) -> usize { x.len() }
assert_eq!(Ok(2).unwrap_or_else(count), 2);
assert_eq!(Err("foo").unwrap_or_else(count), 3);

// unwrap_or_default方法
let x: Result<u32, &str> = Ok(9);
assert_eq!(x.unwrap_or_default(), 9);
let x: Result<u32, &str> = Err("error");
assert_eq!(x.unwrap_or_default(), 0);

// unwrap_err方法
let x: Result<u32, &str> = Err("emergency failure");
assert_eq!(x.unwrap_err(), "emergency failure");

捕获 panic 并阻止栈回退

可以使用std::panic::catch_unwind来实现:

use std::panic;

fn main() {
    let result = panic::catch_unwind(|| {
       println!("hello!"); 
    });
    assert!(result.is_ok());
    let result = panic::catch_unwind(|| {
       panic!("oh no!");
    });
    assert!(result.is_err());
    println!("done");
}

第三方库

thiserror

thiserror 提供了一个派生宏(derive macro)来简化错误类型的定义。主要用于 lib 程序。

地址: https://crates.io/crates/thiserror

use thiserror::Error;

#[derive(Error, Debug)]
pub enum DataStoreError {
    #[error("data store disconnected")]
    Disconnect(#[from] io::Error),
    #[error("the data for key `{0}` is not available")]
    Redaction(String),
    #[error("invalid header (expected {expected:?}, found {found:?})")]
    InvalidHeader {
        expected: String,
        found: String,
    },
    #[error("unknown data store error")]
    Unknown,
}

anyhow

anyhow 实现了 anyhow::Error 和任意符合 Error trait 的错误类型之间的转换,让你可以使用 ? 操作符,不必再手工转换错误类型。

建议在 bin 程序里使用,不要在 lib 程序里使用。

地址: https://crates.io/crates/anyhow

use anyhow::Result;

fn get_cluster_info() -> Result<ClusterMap> {
    let config = std::fs::read_to_string("cluster.json")?;
    let map: ClusterMap = serde_json::from_str(&config)?;
    Ok(map)
}

参考资料