Rust 中的错误处理基本分为 panic 和 Result

panic

非必要不要使用

panic 是线程级别的,即不影响其他线程的使用

panic! 允许接受类似 pringln! 类型的参数

Result

Rust 是没有异常的,如果存在错误,都是使用一个 Result 类型使用的

fn get_weather(location: LatLng) -> Result<WeatherReport,io::Error>

函数返回是 Ok(weather) 或者是 Err(error_value)

对于错误的捕获和处理,可以使用类似 match 的方式

match get_weather(hometown) {
    Ok(report) => {
        display_weather(hometown, &report);
    }
 
    Err(err) => {
        println!("error querying the weather: {}", err);
 
        schedule_weather_retry();
    }
 
}

但是这种方式还是很繁琐,因为 Result 内置了一些方法,可以快速处理希望的操作:

  • result.is_ok() 和 result.is_err() 返回 bool 值,告诉我们 result 是成功的结果还是错误的结果
  • result.ok() 返回 Option 类型的成功值(如果有的话)。如 果 result 是一个成功的结果,就返回 Some(success_value);否 则,返回 None,而丢弃错误值。
  • result.err() 返回 Option 类型的错误值(如果有的话)
  • result.unwrap_or(fallback) 返回成功值,如果 result 是成功的结果的话。否则,它返回 fallback,丢弃错误值
  • result.unwrap_or_else(fallback_fn) 是类似的,只是传入的不 是后备值,而是一个函数或闭包。这个方法适合计算后备值如果 用不上会造成浪费的情况。只有在返回错误结果时才会调用 fallback_fn。
  • result.unwrap() 也会返回成功值(如果 result 是成功的结果 的话)。不过,如果 result 是错误的结果,这个方法则会panic
  • result.expect(message) 与 .unwrap() 相同,只不过你需要自己 提供panic时打印到控制台的消息

除了 .is_ok() 和 .is_err() 之外,其他方法都会 调用它们的 result 值。换句 话说,它们通过 self 参数得到了 result 的值 而这正是 .as_ref() 和 .as_mut() 派上用场的时候

打印错误

println!("error querying the weather: {}", err);
// 打印详细信息
println!("error: {:?}", err)

err.description() 返回 &str 类型的错误消息 err.cause() 返回一个 Option<&Error>,这是触发 err 的底层错误(如果有的话)

传播错误

如果希望出现错误,直接 return ,沿着栈继续向上传播

let weather = get_weather(hometown)?;
 
// 等价于
let weather = match get_weather(hometown) {
    Ok(success_value) => success_value,
    Err(err) => return Err(err)
};

? 操作符的行为取决于这个函数是返回一个成功结果,还是返回一个错误结果。

  • 如果是成功结果,那么它会打开 Result 并取出其中的成功值。 这里 weather 的类型不是 Result<WeatherReport, io::Error>, 而是简单的 WeatherReport。
  • 如果是错误结果,那么它会立即从闭合函数中返回,将错误结果 沿调用链向上传播。为确保传播成功,只能对返回类型为 Result 的函数使用 ?。
use std::fs;
use std::io;
use std::path::Path;
 
fn move_all(src: &Path, dst: &Path) -> io::Result<()> { 
for entry_result in src.read_dir()? { // 打开dir可能失败 
	let entry = entry_result?; // 读取dir可能失败
	let dst_file = dst.join(entry.file_name());
	fs::rename(entry.path(), dst_file)?; // 重命名可能失败 }
	Ok(()) // 哇哦! }

处理 ”不会发生“ 的错误

某些情况下,我们就 某个错误不会发生 最好的办法是使用 .unwrap()

let num = digits.parse::<u64>().unwrap();

使用 .unwrap() 或 .expect(message) 来省略 Result 是可以接受的

忽略错误

let _ = writeln!(stderr(), "error: {}", err); // 没问题,忽略结果

main 中处理错误

当错误传递到main 的时候,就不能继续向上传播了

fn main() {
	calculate_tides().expect("error"); // 麻烦到此为止 
}

如果 calculate_tides() 返回一个错误结果,.expect() 方法则会诧 异。主线程中的诧异会打印错误消息,然后以一个非零退出码退出。

不过错误信息还是打印的太多了

$ tidecalc --planet mercury
thread 'main' panicked at 'error: "moon not found"',
/buildslave/rust-buildbot/s
lave/nightly-dist-rustc-linux/build/src/libcore/result.rs:837
note: Run with `RUST_BACKTRACE=1` for a backtrace.

错误消息混在了一堆文字中间。同样,在这种情况下 RUST_BACKTRACE=1 不是个好主意。最好还是自己来打印错误消息:

fn main() {
    if let Err(err) = calculate_tides() {
        print_error(&err);
        std::process::exit(1);
    }
}
$ tidecalc --planet mercury
error: moon not found