Rust 的异常处理与其他语言有很大不同,主要通过两种机制:panic! 和 Result<T, E>。
1. 不可恢复错误:Panic
panic! 宏
fn main() {
// 程序会立即终止,展开堆栈
panic!("crash and burn");
// 也可以这样使用
if true {
panic!("Something went wrong!");
}
}
设置 panic 行为
// 在 Cargo.toml 中设置
[profile.release]
panic = 'abort' // 直接终止,不清理内存(更小的二进制文件)
[profile.dev]
panic = 'unwind' // 默认:展开堆栈,调用析构函数
2. 可恢复错误:Result 类型
Result 枚举
enum Result<T, E> {
Ok(T), // 成功
Err(E), // 错误
}
基本用法
use std::fs::File;
use std::io::Error;
fn read_file() -> Result<String, std::io::Error> {
let mut file = File::open("hello.txt")?; // ? 运算符传播错误
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
3. 错误处理方式
使用 match
let result = File::open("hello.txt");
match result {
Ok(file) => {
println!("File opened successfully: {:?}", file);
},
Err(error) => {
match error.kind() {
std::io::ErrorKind::NotFound => {
println!("File not found");
},
other_error => {
println!("Error opening file: {:?}", other_error);
}
}
}
}
便捷方法
// unwrap - 成功返回值,失败则 panic
let file = File::open("hello.txt").unwrap();
// expect - 类似 unwrap,可自定义错误信息
let file = File::open("hello.txt")
.expect("Failed to open hello.txt");
// unwrap_or - 提供默认值
let value: i32 = "not_a_number".parse().unwrap_or(0);
// unwrap_or_else - 通过闭包提供默认值
let file = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap()
} else {
panic!("Problem opening the file: {:?}", error);
}
});
4. ? 运算符
use std::fs;
use std::io;
fn read_username_from_file() -> Result<String, io::Error> {
// ? 运算符会自动将错误转换为返回类型
let mut s = String::new();
fs::File::open("hello.txt")?
.read_to_string(&mut s)?;
Ok(s)
}
// 更简洁的写法
fn read_username_from_file() -> Result<String, io::Error> {
fs::read_to_string("hello.txt")
}
5. 自定义错误类型
使用 thiserror 库(推荐)
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MyError {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Parse error: {0}")]
Parse(#[from] std::num::ParseIntError),
#[error("Custom error: {message}")]
Custom { message: String },
}
fn process() -> Result<(), MyError> {
let num = "abc".parse::<i32>()?; // 自动转换为 MyError
Ok(())
}
手动实现
use std::fmt;
use std::error::Error;
#[derive(Debug)]
struct MyError {
details: String,
}
impl MyError {
fn new(msg: &str) -> MyError {
MyError { details: msg.to_string() }
}
}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.details)
}
}
impl Error for MyError {
fn description(&self) -> &str {
&self.details
}
}
6. 错误转换
// 使用 map_err
fn string_length(path: &str) -> Result<usize, String> {
std::fs::read_to_string(path)
.map(|s| s.len())
.map_err(|e| format!("Failed to read file: {}", e))
}
// 使用 From trait 自动转换
impl From<std::io::Error> for MyError {
fn from(error: std::io::Error) -> Self {
MyError::new(&format!("IO error: {}", error))
}
}
7. 错误传播模式
// 链式调用
fn process_data() -> Result<Data, MyError> {
let input = read_input()?
.validate()?
.transform()?
.finalize()?;
Ok(input)
}
// 组合多个结果
fn process_multiple() -> Result<(), MyError> {
let result1 = fallible_op1();
let result2 = fallible_op2();
// 使用 and_then 链式调用
result1.and_then(|r1| {
result2.map(|r2| r1 + r2)
})?;
Ok(())
}
8. 最佳实践
-
库代码:
-
返回
Result<T, E>而不是 panic -
定义明确的错误类型
-
使用
thiserror或anyhow库
-
-
应用程序:
-
在 main 函数中处理错误
-
使用
anyhow简化错误处理 -
只在不可恢复的情况下使用 panic
-
-
测试:
#[test] #[should_panic(expected = "out of bounds")] fn test_panic() { let v = vec![1, 2, 3]; v[99]; }
9. 实用库推荐
-
thiserror:用于定义自定义错误类型
-
anyhow:用于应用程序的错误处理
-
color-eyre:带彩色输出的错误报告
-
miette:漂亮的诊断错误
Rust 的错误处理强调显式和编译时检查,这使得错误处理更加可靠和安全。通过类型系统确保所有可能的错误路径都被考虑,避免了意外的未处理异常。
Rust 提供了多种策略来处理未预期的错误,避免程序崩溃。
1. 顶层错误处理
在 main 函数中处理
fn main() {
if let Err(e) = run_application() {
eprintln!("Application error: {}", e);
// 记录错误日志
log_error(&e);
// 优雅关闭资源
cleanup_resources();
// 非零退出码
std::process::exit(1);
}
}
fn run_application() -> Result<(), Box<dyn std::error::Error>> {
// 所有应用逻辑
your_logic_here()?;
Ok(())
}
2. 使用 anyhow 库简化错误处理
use anyhow::{Context, Result, bail};
use anyhow::anyhow;
fn process() -> Result<()> {
// 自动包装所有错误类型
let data = read_config()
.context("Failed to read config")?;
process_data(&data)
.with_context(|| format!("Processing failed for {:?}", data))?;
Ok(())
}
// 在 main 中使用
fn main() -> Result<()> {
// 自动格式化漂亮的错误链
process()?;
Ok(())
}
3. 设置全局 panic 钩子
use std::panic;
use std::process;
// 在 main 函数开始时设置
fn main() {
// 设置自定义 panic 处理器
panic::set_hook(Box::new(|panic_info| {
eprintln!("===== UNEXPECTED PANIC =====");
eprintln!("Message: {:?}", panic_info.payload().downcast_ref::<String>());
eprintln!("Location: {:?}", panic_info.location());
// 记录堆栈跟踪
#[cfg(debug_assertions)]
{
eprintln!("Backtrace:");
eprintln!("{:?}", backtrace::Backtrace::new());
}
// 尝试保存用户数据
emergency_save();
eprintln!("Application will now exit");
}));
// 运行主逻辑
run_app();
}
// 或者使用更强大的 human-panic
fn main() {
human_panic::setup_panic!();
// 你的代码
}
4. 隔离可能 panic 的代码
使用 catch_unwind
use std::panic;
fn safe_call<F, R>(f: F) -> Result<R, String>
where
F: FnOnce() -> R + panic::UnwindSafe,
{
match panic::catch_unwind(f) {
Ok(result) => Ok(result),
Err(_) => {
// 处理 panic
log::error!("Function panicked, recovering...");
Err("Function panicked".to_string())
}
}
}
// 使用示例
fn process_untrusted_input(input: &str) -> Result<String, String> {
safe_call(|| {
// 可能 panic 的代码
if input.is_empty() {
panic!("Empty input!");
}
format!("Processed: {}", input)
})
}
5. 线程级别的隔离
use std::thread;
use std::time::Duration;
fn main() {
// 启动监控线程
let monitor = thread::spawn(|| {
loop {
// 检查各线程状态
thread::sleep(Duration::from_secs(5));
}
});
// 工作线程,单个线程崩溃不会影响整个程序
let workers: Vec<_> = (0..4)
.map(|id| {
thread::spawn(move || {
if let Err(e) = work(id) {
eprintln!("Worker {} failed: {}", id, e);
// 线程退出,但进程继续
}
})
})
.collect();
// 等待工作线程完成
for worker in workers {
let _ = worker.join();
}
// 关闭监控
monitor.join().unwrap();
}
6. 监控和自动恢复
struct ResilientService {
max_restarts: usize,
restart_delay: Duration,
}
impl ResilientService {
fn run_with_recovery<F>(&self, mut task: F)
where
F: FnMut() -> Result<(), String> + Send + 'static,
{
let mut restart_count = 0;
while restart_count < self.max_restarts {
match task() {
Ok(_) => break, // 成功完成
Err(e) => {
restart_count += 1;
eprintln!("Restart #{}, error: {}", restart_count, e);
if restart_count >= self.max_restarts {
eprintln!("Max restarts reached, giving up");
break;
}
thread::sleep(self.restart_delay);
}
}
}
}
}
7. 配置驱动的错误处理策略
enum ErrorHandlingStrategy {
Ignore, // 忽略错误继续执行
Log, // 记录日志并继续
Retry(usize), // 重试指定次数
Fallback, // 使用备用逻辑
Halt, // 停止当前操作
Exit, // 退出程序
}
struct ApplicationConfig {
panic_strategy: PanicStrategy,
error_strategy: HashMap<String, ErrorHandlingStrategy>,
max_restarts: usize,
}
impl Application {
fn handle_error(&self, error: &dyn Error, context: &str) {
let strategy = self.config.error_strategy
.get(context)
.unwrap_or(&ErrorHandlingStrategy::Log);
match strategy {
ErrorHandlingStrategy::Ignore => {}
ErrorHandlingStrategy::Log => {
eprintln!("[{}] Error: {}", context, error);
}
ErrorHandlingStrategy::Retry(times) => {
self.retry_operation(context, *times);
}
ErrorHandlingStrategy::Fallback => {
self.use_fallback(context);
}
ErrorHandlingStrategy::Halt => {
self.halt_operation(context);
}
ErrorHandlingStrategy::Exit => {
std::process::exit(1);
}
}
}
}
8. 优雅降级
enum ServiceQuality {
Full, // 完整功能
Degraded, // 降级运行
Minimal, // 最小功能
Offline, // 仅本地功能
}
impl Application {
fn determine_service_quality() -> ServiceQuality {
// 检查依赖服务
if !self.check_database() {
return ServiceQuality::Degraded;
}
if !self.check_cache() {
return ServiceQuality::Minimal;
}
if !self.check_network() {
return ServiceQuality::Offline;
}
ServiceQuality::Full
}
fn run_with_graceful_degradation(&self) {
match self.determine_service_quality() {
ServiceQuality::Full => self.run_full_features(),
ServiceQuality::Degraded => {
log::warn!("Running in degraded mode");
self.run_basic_features();
}
ServiceQuality::Minimal => {
log::error!("Running in minimal mode");
self.run_essential_only();
}
ServiceQuality::Offline => {
log::error!("Running offline");
self.run_local_only();
}
}
}
}
9. 健康检查和看门狗
struct Watchdog {
health_check_interval: Duration,
unhealthy_threshold: usize,
}
impl Watchdog {
fn monitor<F>(&self, mut health_check: F)
where
F: FnMut() -> bool + Send + 'static,
{
let mut unhealthy_count = 0;
thread::spawn(move || {
loop {
thread::sleep(self.health_check_interval);
if health_check() {
unhealthy_count = 0; // 健康,重置计数
} else {
unhealthy_count += 1;
eprintln!("Health check failed {} times", unhealthy_count);
if unhealthy_count >= self.unhealthy_threshold {
eprintln!("Watchdog: restarting service...");
self.restart_service();
unhealthy_count = 0;
}
}
}
});
}
}
10. 错误边界模式
trait ErrorBoundary {
type Output;
fn execute(&self) -> Result<Self::Output, String>;
fn on_error(&self, error: String) -> Self::Output;
fn finally(&self) {}
}
impl<F, T> ErrorBoundary for F
where
F: Fn() -> Result<T, String>,
T: Default,
{
type Output = T;
fn execute(&self) -> Result<T, String> {
(self)()
}
fn on_error(&self, error: String) -> T {
eprintln!("Error boundary caught: {}", error);
T::default() // 返回安全默认值
}
}
// 使用
fn process_with_boundary() {
let boundary = || -> Result<String, String> {
dangerous_operation()?;
Ok("Success".to_string())
};
let result = if let Ok(value) = boundary.execute() {
value
} else {
boundary.on_error("Operation failed".to_string())
};
boundary.finally();
}
最佳实践总结
-
顶层捕获:在 main 函数或最外层使用
catch_unwind或 panic 钩子 -
隔离失败:将可能失败的部分隔离在单独线程或进程中
-
监控重启:对关键服务实现监控和自动重启
-
优雅降级:准备好降级方案,在部分功能失效时仍能提供服务
-
资源清理:确保 panic 时能正确释放资源
-
记录诊断:记录足够的诊断信息以便调试
-
用户友好:向用户显示友好的错误信息,而不是崩溃堆栈
通过这些策略,即使遇到未预期的错误,程序也能优雅地处理,避免崩溃,保持最大可能的可用性。
6万+

被折叠的 条评论
为什么被折叠?



