Skip to content

Rust - 错误处理

Rust 非常重视错误处理。其设计理念要求程序员识别并处理潜在的故障,从而生成异常健壮的程序。Rust 将错误分为两大类:

类别描述机制
可恢复错误(Recoverable Errors)可以合理预期并处理的错误,例如文件未找到或用户输入无效。Result<T, E> 枚举
不可恢复错误(Unrecoverable Errors)表示无法恢复的 Bug 或状态的错误,例如访问数组越界索引。panic! 宏

与许多使用异常(exceptions)的语言不同,Rust 使用返回类型系统来编码错误信息,强制你在编译时处理它。

使用 panic! 进行不可恢复错误处理

Section titled “使用 panic! 进行不可恢复错误处理”

panic! 宏表示你的程序已达到不可恢复状态。当它被调用时,你的程序将打印一条失败消息,展开并清理栈,然后退出。它应该保留用于真正的编程错误。

fn main() {
// This is an example of an explicit panic.
// In a real application, this might be triggered by a critical invariant being violated.
panic!("A critical error occurred! Shutting down.");
// This line will never be reached.
println!("This will not be printed.");
}
thread 'main' panicked at 'A critical error occurred! Shutting down.', src/main.rs:4:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

许多常见的操作,例如访问无效的数组索引,也会导致 panic。

fn main() {
let numbers = [1, 2, 3];
numbers[99]; // This will panic because the index is out of bounds.
}

使用 Result<T, E> 进行可恢复错误处理

Section titled “使用 Result<T, E> 进行可恢复错误处理”

对于偶尔会发生的错误,Rust 提供了 Result<T, E> 枚举。它的定义如下:

enum Result<T, E> {
Ok(T), // Contains the success value
Err(E), // Contains the error value
}

一个可能失败的函数将返回一个 Result。如果成功,它返回包含结果的 Ok;如果失败,它返回包含错误信息的 Err。

处理 Result 最基本的方法是使用 match 表达式,它强制你处理 Ok 和 Err 两种情况。

use std::fs::File;
fn main() {
let file_result = File::open("non_existent_file.txt");
let my_file = match file_result {
Ok(file) => {
println!("File opened successfully.");
file
},
Err(error) => {
// We gracefully handle the error instead of crashing.
panic!("Problem opening the file: {:?}", error);
},
};
println!("Program continues after handling the Result.");
}

到处编写 match 表达式可能很冗长。对于返回 Result 的函数,Rust 提供了强大的问号(?)运算符来简化错误传播。

如果 Result 的值是 Ok,Ok 内部的值将从此表达式返回,程序将继续执行。如果值是 Err,则 Err 将从整个函数返回,就好像我们使用了 return 关键字一样。

use std::fs::File;
use std::io::{self, Read};
// This function reads a username from a file.
// It returns a Result, so we can use the `?` operator inside it.
fn read_username_from_file() -> Result<String, io::Error> {
let mut username_file = File::open("hello.txt")?;
// If `File::open` fails, `?` causes the function to return the `Err` immediately.
let mut username = String::new();
username_file.read_to_string(&mut username)?;
// If `read_to_string` fails, `?` returns that error.
Ok(username)
// If everything succeeds, we wrap the result in `Ok`.
}
fn main() {
// In main, we handle the final result.
match read_username_from_file() {
Ok(username) => println!("Username: {}", username),
Err(e) => println!("Failed to read username: {}", e),
}
}

? 运算符极大地简化了错误处理代码,是 Rust 中传播错误的惯用方式。

发生错误时触发 panic 的快捷方式:unwrap 和 expect

Section titled “发生错误时触发 panic 的快捷方式:unwrap 和 expect”

Result<T, E> 类型有两个辅助方法,在特定情况下(例如在测试或快速原型中)很有用。在生产代码中请谨慎使用它们。

  • unwrap():如果 Result 是 Ok,它返回其中的值。如果是 Err,它将以默认消息 panic!。
  • expect("message"):与 unwrap() 类似,但允许你提供自定义 panic 消息。
use std::fs::File;
fn main() {
// Use `expect` when you are certain an operation should not fail,
// and if it does, it's a critical bug.
let greeting_file = File::open("hello.txt")
.expect("hello.txt should be included in this project");
// Using unwrap is less descriptive.
// let greeting_file = File::open("hello.txt").unwrap();
}
  • 优先使用 Result 而非 panic!:对于正常操作中任何可能发生的错误,请使用 Result。
  • 使用 ? 运算符:在返回 Result 的函数中,接受 ? 运算符以实现简洁和惯用的错误传播。
  • 定义自定义错误类型:对于库或大型应用程序,创建自己的错误枚举以提供更具体和有用的错误信息。
  • 谨慎使用 unwrap 和 expect:将它们保留用于测试、示例或当出现问题时 panic 确实是正确行为的情况。