Ch 4: std::panic - 恐慌与恢复
panic 是 Rust 中用于处理不可恢复错误的机制。当程序遇到无法继续执行的情况时,会触发 panic。本章将详细讲解 panic 的工作原理、如何捕获 panic、以及如何自定义 panic 处理行为。
2. panic! 宏 - 触发恐慌
Section titled “2. panic! 宏 - 触发恐慌”panic! 宏用于主动触发 panic,通常用于表示程序遇到了无法恢复的错误:
基本用法:
fn main() { println!("程序开始...");
panic!("这是一个恐慌!");
println!("这行不会执行");}运行结果:
thread 'main' panicked at '这是一个恐慌!', src/main.rs:4:5note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.3. panic! 的两种终止方式
Section titled “3. panic! 的两种终止方式”Rust 程序在 panic 时可以选择” unwind “或” abort “两种终止方式:
- Unwind(默认):展开调用栈,调用所有局部变量的析构函数。
- Abort:直接终止程序,不展开栈。
// Cargo.toml 中可以设置[profile.release]panic = "abort" # 发布版本默认 abort4. assert! 宏 - 断言
Section titled “4. assert! 宏 - 断言”assert! 是基于 panic! 的断言宏:
基本用法:
fn main() { let x = 5; assert!(x > 0, "x 应该是正数,但它是 {}", x);
let v = vec![1, 2, 3]; assert_eq!(v.len(), 3, "向量长度应该是 3");}标准库提供的断言宏:
// assert_eq! - 断言相等assert_eq!(2 + 2, 4);
// assert_ne! - 断言不等assert_ne!(2 + 2, 5);
// debug_assert! - 仅在调试模式生效debug_assert!(true);5. catch_unwind - 捕获恐慌
Section titled “5. catch_unwind - 捕获恐慌”std::panic::catch_unwind 允许捕获 panic,使程序不会终止:
方法签名:
pub fn catch_unwind<F, T>(f: F) -> Result<T, Box<dyn Any + Send>>where F: FnOnce() -> T + UnwindSafe,示例:
use std::panic;
fn may_panic() { println!("即将 panic..."); panic!("Oops!");}
fn main() { let result = panic::catch_unwind(may_panic);
match result { Ok(_) => println!("没有 panic"), Err(e) => { println!("捕获到 panic: {:?}", e); } }
println!("程序继续执行");}注意返回值的类型:
use std::panic;
fn main() { let result = panic::catch_unwind(|| { panic!("something went wrong"); });
if result.is_err() { println!("捕获到 panic"); }
// 可以使用 downcast 获取 panic 信息 let result = panic::catch_unwind(|| { panic!("error code: {}", 42); });
if let Err(e) = result { if let Some(s) = e.downcast_ref::<&str>() { println!("panic message: {}", s); } }}6. AssertUnwindSafe - 标记线程安全
Section titled “6. AssertUnwindSafe - 标记线程安全”AssertUnwindSafe 用于标记在 panic 后仍可以安全使用的值:
使用场景 - 与 catch_unwind 配合:
use std::panic::{catch_unwind, AssertUnwindSafe};
fn main() { let mut data = vec![1, 2, 3];
// 如果闭包可能 panic,但你想在 catch 后继续使用数据 let result = catch_unwind(AssertUnwindSafe(|| { data.push(4); if data.len() > 10 { panic!("Too many elements!"); } }));
println!("result: {:?}", result); // Ok(()) 或 Err(...)
// 正常情况下 data 仍然可用 println!("data: {:?}", data);}为什么不默认是 UnwindSafe:
use std::panic::{catch_unwind, UnwindSafe};
struct NotThreadSafe { data: Vec<u32>,}
impl UnwindSafe for NotThreadSafe {}7. set_hook - 自定义Panic处理
Section titled “7. set_hook - 自定义Panic处理”set_hook 允许你自定义 panic 发生时的行为:
基本用法:
use std::panic;
fn main() { // 保存原来的 hook let original_hook = panic::take_hook();
// 设置新的 hook panic::set_hook(Box::new(|panic_info| { let msg = if let Some(s) = panic_info.payload().downcast_ref::<&str>() { s.to_string() } else if let Some(s) = panic_info.payload().downcast_ref::<String>() { s.clone() } else { "Unknown panic".to_string() };
let location = panic_info.location() .map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column())) .unwrap_or_else(|| "unknown location".to_string());
eprintln!("[PANIC] {} at {}", msg, location); }));
panic!("Test panic");
// 恢复原来的 hook panic::set_hook(original_hook);}将 panic 信息写入日志:
use std::panic;use std::fs::OpenOptions;use std::io::Write;
fn setup_panic_handler() { panic::set_hook(Box::new(|panic_info| { let msg = if let Some(s) = panic_info.payload().downcast_ref::<&str>() { s.to_string() } else { "Unknown".to_string() };
let location = panic_info.location() .map(|loc| format!("{}:{}", loc.file(), loc.line())) .unwrap_or_else(|| "unknown".to_string());
let timestamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs();
let log_line = format!("[{}] PANIC at {}: {}\n", timestamp, location, msg);
if let Ok(mut file) = OpenOptions::new() .create(true) .append(true) .open("panic.log") { let _ = file.write_all(log_line.as_bytes()); } }));}
fn main() { setup_panic_handler(); panic!("Test");}8. PanicInfo - Panic信息
Section titled “8. PanicInfo - Panic信息”PanicInfo 包含 panic 的详细信息:
use std::panic;
fn main() { panic::set_hook(Box::new(|panic_info| { // payload - panic 携带的数据 if let Some(msg) = panic_info.payload().downcast_ref::<&str>() { println!("Payload: {}", msg); }
// location - panic 发生的位置 if let Some(loc) = panic_info.location() { println!("File: {}", loc.file()); println!("Line: {}", loc.line()); println!("Column: {}", loc.column()); } }));
panic!("Error at some point");}9. resume_unwind - 继续传播Panic
Section titled “9. resume_unwind - 继续传播Panic”resume_unwind 用于重新触发被捕获的 panic:
use std::panic;
fn main() { let result = panic::catch_unwind(|| { panic!("original panic"); });
if let Err(e) = result { println!("捕获到 panic,继续传播");
// 重新触发 panic resume_unwind(e); }}10. 需要注意的点
Section titled “10. 需要注意的点”-
panic!不是用于错误处理:正常情况下应使用Result和Option,只有在真正无法恢复时才使用panic!。 -
catch_unwind有性能开销:每次捕获 panic 都需要展开栈,在性能敏感路径上要谨慎使用。 -
Rust 默认是 unwind:发布版本可以切换为 abort 以减小二进制大小。
-
panic!会触发析构函数:unwind 时会调用所有局部变量的析构函数。 -
catch_unwind只能捕获 unwind:如果程序设置为 abort 模式,catch_unwind无法捕获。 -
UnwindSafetrait:某些类型在 panic 后可能处于不一致状态,Rust 用UnwindSafe标记来帮助检测。
11. 实践练习
Section titled “11. 实践练习”- 编写一个程序,使用
catch_unwind捕获可能 panic 的计算。 - 实现一个自定义的 panic hook,将 panic 信息写入文件。
- 研究
panic=abort对程序行为的影响。 - 实现一个函数,用
catch_unwind包装一个可能 panic 的 FFI 调用。
12. 总结
Section titled “12. 总结”std::panic 模块提供了 Rust 恐慌处理的核心功能:
| 组件 | 作用 |
|---|---|
panic! | 触发 panic |
catch_unwind | 捕获 panic |
AssertUnwindSafe | 标记线程安全的值 |
PanicInfo | panic 详细信息 |
set_hook | 自定义 panic 处理器 |
resume_unwind | 继续传播 panic |
理解 panic 机制对于编写健壮的 Rust 程序至关重要,特别是在编写库或与外部代码交互时。