无论使用什么编程语言,错误处理都是很重要的一块。如果在生产环境中,错误没有得到妥善合理的处理,就有可能给系统埋下隐患。各种语言错误处理方式有所差异,比如 C 语言使用返回值,Go 语言在调用时返回 error,程序中充斥着if err != nil {}
,Java使用异常来做处理,实现了错误的产生和处理分离开,但是很容易被滥用,造成额外的开销。
Rust 走了一条完全不同的路,使用类型系统来进行错误处理。Rust 将错误分为两大类:可恢复的(recoverable)和 不可恢复的(unrecoverable)错误。Rust使用Result<T, E>
类型用于处理可恢复的错误,使用panic!
宏来在遇到不可恢复的错误时停止运行。但是 Rust 还有着更多的错误处理方式。
断言
Rust 标准库提供 6 个常用断言,断言失败的话就会引发程序恐慌崩溃。
assert!
: 用于断言布尔表达式结果一定为 trueassert_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
: 通过闭包函数,将函数应用于包含的Some
或None
,并将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
就返回 trueis_ok_and
: 如果值为Ok
且闭包匹配成功,则返回 trueis_err
: 如果值内容是Err
就返回 trueis_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)
}