Rust中没有异常的概念,也没有其他语言中 try ... catch ... 的语法结构。取而代之的是每个可能会产生错误的函数和方法都需要返回一个 Result 类型来提供返回值和错误信息。如果一个函数或者方法返回了 Result 类型的值,那么就代表这个函数或方法可能会执行失败,必须要对其可能发生的错误进行处理。
这里需要注意的是,Rust程序没有一个异常状态,取而代之的是一个panic状态。在进入panic状态以后,程序的默认动作是展开栈,当然也可以选择中止进程。在程序中调用宏 panic!() 可以手工触发panic状态,其用法与宏 println!() 类似。panic状态并不是程序的崩溃状态,不违反Rust的任何安全原则,并且panic是线程级别的,在一个线程进入panic状态时,其他的线程不会受到影响。
Result 类型的常见返回类型为 Result<T, io::Error> ,在这个返回类型中,可以使用 Ok() 来获取类型为 T 的返回值,使用 Err() 来获取错误结果。
Result 类型的定义
Rust标准库中的 Result 类型实际上是一个泛型枚举类型,其定义如下:
enum Result<T, E> {Ok(T),Err(E)}
Result 类型还有一种别名的写法:Result<T> ,它相当于 Result<T, std::io::Error> 。这种写法可以在一个模块中避免连续重复多次书写同一个错误类型。
错误的捕获
既然 Result 类型是一个枚举类型,那么处理其中的内容就可以使用 match 表达式了。这时的 match 表达式相当与其他语言中的 try ... catch ... 语法结构。其用法也很简单:
match something_may_wrong() {Ok(report) => {// 处理正常运行的返回值,返回值在变量report里}Err(err) => {// 处理出现错误时的错误信息,错误信息在变量err里}}
对于 match 表达式的啰嗦,Rust也针对一些常见用法提供了快速处理函数可供使用。
result.is_ok()和result.is_err(),返回一个bool值,用于判断result是成功的还是失败的。result.ok(),返回Option<T>类型的成功值,如果result是成功的,那么可以使用Some(value)获取返回值;否则会返回None丢弃错误值。result.err(),返回Option<E>类型的错误值,行为与result.ok()正好相反。result.unwrap_or(default),如果result是成功的,会返回类型为T的返回值,而不是Option<T>类型;如果result是失败的,则会返回给定的默认值,同时丢弃错误值。result.unwrap_or_else(fallback_fn),如果result是成功的,会返回类型为T的返回值;如果result是失败的,则会使用给定的函数或者闭包生成一个后备值,这个函数或者闭包会接受result中携带的错误值作为参数。result.unwrap(),如果result是成功的,会返回类型为T的返回值;否则会使所在线程进入panic状态。result.expect(message),如果result是成功的,会返回类型为T的返回值;否则需要提供一个进入panic状态后输出在控制台的错误信息。result.as_ref(),将类型Result<T, E>转换为Result<&T, &E>,可直接借用目前result中的成功值和错误值的引用。result.as_mut(),将类型Result<T, E>转换为Result<&mut T, &mut E>。错误输出
在
Result类型中携带的错误信息可以直接使用println!()来输出到控制台,例如 :match some_result {Ok(report) => {}Err(err) => {println!("Error occured: {}", err);}}
除此之外,还可以调用以下两个方法来获取更多的信息。
err.description(),返回&str类型的错误信息。err.cause(),返回一个Option<&Error>。错误传播
在大多数情况下,错误不一定都需要在它发生的地方处理,这样就需要将错误抛至上一级调用进行处理了。这个就像是Java中的throws:
在Rust中把函数中返回的public void throw_to_uplevel() throws IOError {// ...throw new IOError("Some Error");// ...}
Result<T, E>抛出到上一级,只需要在函数调用时添加一个?操作符即可。这样,当函数调用返回错误时,Rust就会把错误信息沿着调用链向上传播。例如:
这里需要注意的是,如果已经到达程序的let result = may_be(something)?;
main()函数,那么就不能继续向上传播错误了,如果需要程序停下来,可以直接使用.expect()。或者可以使用以下代码输出更加有亲和力的错误信息。fn main() {if let Err(err) = maybe_someerror() {print_error(&err);std::process::exit(1);}}
栈展开
在一个线程进入到panic状态以后,可以使用标准库提供的函数std::panic::catch_unwind()来捕获栈展开。通过使用标准库std::panic中的函数,可以在线程进入到panic状态的时候恢复线程的运行。
栈展开和恢复线程运行的操作在调用C/C++或者其他语言协程的库的时候是十分必要的,因为其他语言编写的库,不会遵守Rust的安全原则。
