外观
雷区 4:expect 的话说了等于没说
症状
rust
fn main() {
let input = String::from("abc");
let number: u32 = input.parse().expect("parse failed");
println!("{}", number);
}运行输出
text
thread 'main' panicked at src/main.rs:3:37:
parse failed: ParseIntError { kind: InvalidDigit }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtraceparse failed——然后呢?用户看到了什么?开发者查 bug 时看到了什么?什么都没有。
诊断
expect 的意义是"崩溃前留一句遗言,帮人定位问题"。但很多遗言是废话:
parse failed:哪个输入?在哪一行?上下文呢?unwrap():连废话都没有assert ok:……说了等于没说
崩溃信息是第一现场:用户截图发你、日志里躺着的那行字,常常是唯一的线索。遗言写得好,三秒定位;写得烂,重现半天。
解药
解药一:遗言要有"上下文三件套"——什么操作、什么数据、什么期望:
rust
fn main() {
let input = String::from("abc");
let number: u32 = input
.parse()
.expect("输入应该是数字,实际输入:abc"); // 期望 + 实际
println!("{}", number);
}rust
let config = fs::read_to_string("config.toml")
.expect("config.toml 必须存在(安装包应该自带默认配置)"); // 什么 + 为什么必须有
let user = users.get(id).expect("用户 ID 来自数据库主键,一定存在"); // 什么 + 为什么不可能失败解药二:期望字符串可以"现场拼"——动态信息也能写进遗言:
rust
fn main() {
let input = String::from("abc");
let number: u32 = input
.parse()
.expect(&format!("用户输入的不是数字:{}", input));
println!("{}", number);
}(注意 &format! 是雷区 E0716 的经典陷阱——expect 的参数在这里是"临时借用",这一句里用完就释放,恰好安全。)
解药三:把"为什么会炸"写进代码,而不是遗言——expect 之前先保证:
rust
let first = numbers.first().expect("调用前已检查 not_empty,这里一定有值");
// 等价且更防呆的写法:if numbers.is_empty() { return ... } 提前处理预防
- expect 遗言 = 给未来同事的留言条:写"什么操作 + 为什么这里不可能失败";写不下就
unwrap的,说明这里不该 unwrap - 三个标准问句:①在哪个函数?②什么数据?③为什么不可能失败?——三问写完,遗言合格
- expect 与 unwrap 的分工:expect 用在"逻辑上不可能失败、但真的失败了说明有 bug"的地方;用户可能造成失败的地方,永远用 match/?
- 真实项目惯例:能
?就不 expect;必须 expect 的地方,遗言里带上操作名和变量值