错误处理是程序的重要组成部分,一个程序的错误处理好坏,关系到程序是否健壮。Rust以安全和高效著称,那Rust的错误处理是怎样的呢?Rust错误处理关键在:避免隐藏错误,暴露错误,错误要处理。不得不提Result<T, E> 类型,普通的错误处理都围绕这该类型展开。Rust还包含panic错误处理,panic会造成运行时恐慌,默认后果是程序异常退出。那什么时候用panic呢?
- 示例代码,用
panic能简化错误处理代码。 - 确定不可能错误的
Result<T, E>变量。 如:let a = Ok(1); let b = a.unwrap();。unwarp的默认行为是panic,使用panic处理错误能简化错误处理。 - 出现不能接受的错误,直接用
panic退出程序,打印调用栈,比蹩脚的错误恢复处理要高明得多。
下面按照惯例看一个例子:
fn main() {
panic!("expect panic");
}
下面将讨论更一般的错误处理机制:Result<T, E>。先看一下它的定义:
enum Result<T, E> {
Ok(T),
Err(E),
}
它的定义很简单,仅包含成功与失败两种可能。在错误处理机制中,我们最关心的就是操作是否成功;如果失败了再具体看原因。但很多时候我们会忽略错误处理,天真的认为不会失败。事实告诉我们在计算机的世界里可能失败的,一定会失败。而Rust为了避免错误被忽略掉,特地给Result加了#[must_use]注解,编译器会在编译期检查Result是否被处理了,用心良苦啊!
-
match处理错误:match get_result() { Ok(ok) => { display_ok(ok); } Err(err) => { display_err(err); } }上面是Rust经典的
match处理模式。 -
?传播错误:fn propagate_result(flag: bool) -> Result<(), &'static str> { get_result(flag)?; Ok(()) } fn get_result(flag: bool) -> Result<(), &'static str> { if flag { return Ok(()); } else { return Err("expect error"); } } fn main() { println!("{:?}", propagate_result(false)); // 输出:Err("expect error") }上面使用
?操作符来处理错误。当我们不希望处理繁琐的错误分支时,使用?来简化错误处理,遇到Err时直接返回Err。 -
忽略错误:
fn get_result(flag: bool) -> Result<(), &'static str> { if flag { return Ok(()); } else { return Err("expect error"); } } fn main() { let _ = get_result(false); }有时候我们不想处理错误,可以向上面这样丢弃错误。
-
自定义错误类型和返回不同错误类型:
#[derive(Debug, Clone)] struct NumError { data: i32, } impl std::fmt::Display for NumError { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { write!(f, "NumError:(data: {})", self.data) } } impl std::error::Error for NumError { fn description(&self) -> &str { "except NumError" } } fn get_result(flag: i32) -> Result<(), Box<dyn std::error::Error>> { if flag < 0 { return Err(Box::new(NumError{data: 1})); } else if flag > 0 { return Err(Box::from(std::io::Error::new( std::io::ErrorKind::Other, "flag great than 0", ))); } else { return Ok(()); } } fn main() { println!("{:?}", get_result(1)); println!("{:?}", get_result(-1)); println!("{:?}", get_result(0)); }输出:
Err(Custom { kind: Other, error: "flag great than 0" }) Err(NumError { data: 1 }) Ok(())上面的例子通过实现
std::error::Error和std::fmt::Display两个trait来实现自定义错误类型。通过Box<dyn std::error::Error>来接收不同的错误类型,实现返回不同的错误类型。