6.2 Panic:当庭宣判与退庭


6.2 Panic:当庭宣判与退庭

本节摘要:panic 表示"程序自身缺陷被抓住"——越界、算术溢出(debug 下)、显式断言失败。本节讲 panic 的行为(栈展开与终止)、backtrace 的读法,以及 panic 与 Result 的分界判据。读完你能在设计时正确归类错误。

什么时候当庭宣判

let v = vec![1, 2, 3]; let x = v[10]; // 越界:panic,打印到 stderr 后退庭 let y: u8 = 257u16 as u8; // 不 panic:显式转换语义由你负责 assert_eq!(2 + 2, 4); // 断言失败即 panic,测试的基础 panic!("证据链断裂,无法继续"); // 显式宣判

panic 后发生什么:默认栈展开,逐层运行析构释放资源,打印调用回溯;也可配置为直接终止(panic = abort),二进制更小、无展开成本,嵌入式常用。回溯用环境变量开启后,从出错点到根因的每一帧都列出来——读法与任何语言的调用栈一致。

分界判据:谁的错

情况 归类 处理
文件不存在、网络超时、解析失败 外部世界的问题 Result,调用方决定
数组越界、不变量被打破、空 Vec 取首 程序自己的 bug panic 或前置检查
调用方传了非法参数且可预期 输入问题 Result 带 InvalidInput 类错误

经验判据一句:如果调用方合理地可能需要恢复,就返回 Result;如果恢复意味着掩盖 bug,就 panic。解析用户输入当然 Result;内部缓存明明刚插入却查不到,panic 更诚实。

图:错误分流决策

图:错误分流决策

要点回顾

  • panic 留给不可恢复的自身缺陷,默认栈展开、可读回溯;
  • abort 模式换体积与确定性,嵌入式与极致二进制的选项;
  • 断言宏家族(assert、assert_eq、debug_assert)是测试章节的地基;
  • unwrap 不是罪,但每一处都应能回答"为什么这里不可能失败"。

panic 的现场勘查工具

panic 一旦发生,第一证物是 backtrace。开关在环境变量上,不必改代码。

fn index_sheet(sheets: &[u8], i: usize) -> u8 { sheets[i] // 越界触发 panic,运行期扣留 } fn main() { std::env::set_var("RUST_BACKTRACE", "1"); let s = [7u8, 9]; println!("{}", index_sheet(&s, 5)); }
thread 'main' panicked at src/main.rs:2:5: index out of bounds: the len is 2 but the index is 5 stack backtrace: 0: rust_begin_unwind 1: core::panicking::panic_fmt 2: demo::index_sheet 3: demo::main note: run with `RUST_BACKTRACE=full` for a verbose backtrace

报错首行给出"什么错、在哪",backtrace 给出"怎么到的"。release 构建里符号可能被裁,需要 debug = true 的 profile 才有完整函数名——勘查配置要先于勘查本身。

panic 与 abort:两种销案方式

panic 默认走 unwind:逐层析构后退出线程,主线程 panic 即进程退出。Cargo.toml 可改为直接 abort:省去 unwind 表、二进制更小、代价是丢失 catch 可能。

模式 行为 体积 场景
unwind(默认) 逐层析构、可被捕获 略大 常规应用
abort 立即终止进程 更小 嵌入式、追求极简

panic = "abort" 的项目里,catch_unwind 也无济于事,第 8 章线程一节讨论的"子线程 panic 不拖垮主进程"的前提也随之消失——配置错误处理策略前先确认这一项。

catch_unwind:有限度的复审

fn risky(v: &[i32]) -> i32 { v.iter().copied().fold(0, |a, b| a.checked_add(b).expect("累加溢出")) } fn main() { let r = std::panic::catch_unwind(|| risky(&[i32::MAX, 1])); match r { Ok(v) => println!("合计 {}", v), Err(_) => println!("外购批次异常,转人工复核"), // panic 被拦下但进程仍在 } }

catch_unwind 不是 try/catch:它只拦 unwind 型 panic,拦不住 abort,也不该用于常规分支逻辑。正当席位是 FFI 边界(panic 穿越 extern "C" 是未定义行为)与测试框架、任务池这类"隔离他人代码爆炸半径"的宿主程序。业务错误用 Result,是本教程反复立的一条铁律。

unwrap 的纪律

unwrap 与 expect 不是禁词,但要有使用判据:unwrap 用于"不可能失败,失败即程序错误"的场合(测试代码、原型、静态不变量);expect 附上说明,把"为什么确信"写进字符串。任何一条可能由外部输入触发的路径都不该出现 unwrap——字符串解析、文件读取、网络收包全在其列。code review 时 unwrap 出现在处理输入的函数里,几乎可以直接判退回。

panic 判例速断卡

现场特征 判定
数组下标、切片边界 程序错误,panic 合理
parse 用户输入失败 可预期坏数据,必须 Result
文件不存在 环境状态,默认 Result
配置值非法 启动期一次性校验,panic 或启动失败皆可

第四行常起争议:启动期 fail-fast 换取运行期零检查,是正当取舍,前提是失败发生在进程入口而非任意请求路径。把这张卡贴在 code review 的检查单里,unwrap 与 expect 的每次出现都要对号入座。

收尾对照实验

用一个十分钟实验把本章两节打通:同一份"读取配置并解析端口"的函数写两版——版本一解析失败就 panic,版本二返回 Result 到 main 统一出口;再准备一份坏配置分别喂给两版。观察差异:panic 版的进程退出信息直指 unwrap 行号,但调用方毫无挽回余地;Result 版的 main 可以在退出前打印配置文件名与行号的上下文。结论落回判词:外部输入走 Result 不是教条,是给运维留勘查现场的契约。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U