===^^===
《榴芒客服系统》是我们工作室开发的在线客服系统,欢迎下载试用:
Rust中的两类错误处理:panic和Result。
普通错误使用Result类型来处理。
panic针对的是另一种错误,即那种永远不应该发生的错误。
当程序出现下列问题时,会引发panic:
数组越界访问;
整数除以0;
在恰好为Err的Result上调用.expect();
断言失败。
Rust既可以在发生panic时展开调用栈,也可以终结进程。展开调用栈时默认方案。
整数除以0,会触发panic,通常会按如下方式处理:
把错误信息打印到终端。
展开调用栈。当前函数使用的任何临时值、局部变量或参数都将按照与创建它们时的相反的顺序被丢弃。丢弃一个值仅仅意味着随后会进行清理:程序正在使用的任何字符串或向量都将被释放,所有打开的文件都将被关闭,等等。还会调用有用户定义的drop方法。
最后,线程退出。如果是主线程,则整个进程退出(使用非零退出码)。
panic不是崩溃,也不是未定义行为。它更像是Java中的RuntimeException或C++中的std::logic_error。
panic是安全的,不会在内存中留下悬空指针或半初始化的值。
panic是基于线程的。一个线程panic时,其它线程可以继续做自己的事。
调用栈展开时默认的panic处理行为,但在两种情况下Rust不会试图展开调用栈。
如果Rust在试图清理第一个panic时,.drop方法触发了第二个panic,那么这个panic就是致命的。Rust会停止展开调用栈并中止整个进程。
如果使用-C panic=abort参数进行编译,那么程序中的第一个panic会立即中止进程。
Rust中没有异常,函数执行失败时会有像下面这样的返回类型:
fn get_weather(location: LatLng) -> Result<WeatherReport, io::Error>
使用match表达式处理Result:
match get_weather(hometown) {
Ok(report) => {
Display_weather(hometown, &report);
}
Err(err) => {
println!("error querying the weather: {}", err);
schedule_weather_retry();
}
}
match有点复杂,一次Result<T, E>针对一些常见的特定场景提供了多个有用的方法:
result.is_ok()和result.is_err(),这两个方法返回bool类型的值,用于表示result是已成功,还是已出错。
result.ok(),返回Option<T>。
如果result是成功的,就返回Some(success_value);否则返回None,并丢弃错误值。
result.err(),以Option<E>类型返回错误值。
result.unwrap_or(fallback),如果result是成功结果,就返回成功值;否则,返回fallback,丢弃错误值。
result.unwrap(),如果result是成功结果,此方法同样会返回成功值;如果result是错误结果,则会引发panic。
result.expect(message),与.unwrap()行为相同,但允许你提供一条消息,在发生panic时打印出该消息。
result.as_ref(),将Result<T, E>转换为Result<&T, &E)。
result.as_mut(),将Result<T, E>转换为Result<&mut T, &mut E)。
类型别名是类型名称的简写形式。标准库中的std::io模块包括下面这行代码:
pub type Result<T> = result::Result<T, Error>;
上面的语句定义了公共类型std::io::Result<T>,它是Result<T, E>的别名,但将错误类型硬编码为std::io::Error。
所有错误类型都可以通过println!()打印出来。使用格式说明符{}打印错误通常只会显示一条简短的错误消息。使用格式说明符{:?},以获得该错误的Debug试图,这对用户不太友好,但包含额外的技术信息。
打印错误信值并不会打印出其来源。如果想确保打印所有可用信息,请使用下面这个函数:
use std::error::Error;
use std:io::{Write, stderr};
/// 把错误信息转储到stderr
/// 如果在构建此错误信息或将其写入stderr期间发生了另一个错误,就忽略新错误
fn print_error(mut err: &dyn Error) {
let _ = writeln!(stderr(), "error: {}", err);
while let Some(source) = err.source() {
let _ = writeln!(stderr(), "caused by: {}", source);
err = source;
}
}
writeln!宏会将数据写入所选的流。在这里,将错误信息写入了标准错误流std::io::stderr。
大多数时候,可能不想立即捕获并处理错误。当发生某种错误时,我们通常希望让调用者去处理。希望错误沿着调用栈向上传播。
?运算符可以实现此操作:
let weather = get_weather(hometown)?;
?的行为取决于此函数是返回了成功结果还是错误结果。
如果是成功结果,那么它会解包Result以获取其中的成功值。
如果是错误结果,那么它会立即从所在函数返回,将错误结果沿着调用链向上传播。?只能在返回类型为Result的函数中的Result值上使用。
?运算符并无任何神奇之处。可以使用match表达式来表达同样的意图:
let weather = match get_weather(hometown) {
Ok(success_value) => success_value,
Err(err) => return Err(err)
}
try!宏,在引入?运算符之前,是船舶错误的常用方法:
let weather = try!(get_weather(hometown));
try!宏会扩展为一个match表达式。
?运算符也能用于Option类型。在返回Option类型的函数中,也可以使用?解包某个值,当遇到None时就会提前返回:
let weather = get_weather(hometown).ok()?;
所有标准库中的错误类型都可以转换为类型:
Box<dyn std::error::Error + Send + Sync + 'static>
为便于使用,还可以定义类型别名:
type GenericError
= Box<dyn std::error::Error + Send + Sync + 'static>;
Type GenericResult<T> = Result<T, GenericError>;
然后,将read_numbers()的返回类型改为GenericResult<Vec<i64>>。这样一来,函数就可以编译了。?运算符会根据需要自动将任意类型的错误转换为GenericError。
GenericError方法的缺点是返回类型不再准确地传达调用者可预期的错误类型。调用者必须做好应对任何情况的准备。
如果你正在调用一个返回GenericResult的函数,并且想要处理一种特定类型的错误,而让所有其它错误传播出去,那么可以使用泛型方法:
error.downcast_ref::<ErrorType>()
main函数不能使用?,因为它的返回类型不是Result:
fn main() {
calculate_tides()?; // 错误,main()无法再推卸责任了
}
处理main()函数中错误的简单方式是使用.expect():
fn main() {
calculate_tides().expect("error"); // 责任止于此
}
如果calculate_tides()返回错误,那么.expect()会panic。主线程中的panic会打印出一条错误消息,然后以非零的退出码退出。
也可以更改main()的类型签名以返回Result类型,这样就可以使用?了:
fn main() -> Result<(), TideCalcError> {
let tides = calculate_tides()?;
print_tides(tides);
Ok(())
}
===^^===
《榴芒客服系统》是我们工作室开发的在线客服系统,欢迎下载试用: