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的安全原则。