趣味学RUST基础篇(错误处理)
错误处理
接上一回的“三大神器”——背包、卷轴、储物柜,现在我们的冒险进入了一个更真实、更刺激的阶段:
Rust 的“防崩大法”:错误处理!
到目前为止,你的代码一路顺风顺水:背包装得下,卷轴写得对,储物柜找得快。
但现实是——
文件打不开!网络连不上!数组越界了!服务器炸了!
这些不是“如果”,而是“早晚的事”。
其他语言怎么说?“别怕,有异常(Exception),出错就抛,抛了就抓,抓了就 retry!”
但 Rust 说:
“不,那样太随意了!我们要提前面对现实。”
Rust 的哲学:错误分两种
Rust 把错误分成两类,就像把敌人分成“能打的”和“Boss 级的”:
可恢复错误:小怪,能打!
比如:
- “文件
save.dat
找不到!” - “网络连接超时,请重试。”
- “密码错了,再输一次?”
这些不是程序崩了,而是用户操作问题,可以重试、可以换方案。Rust 用 Result<T, E>
来处理这种错误。
不可恢复错误:Boss,GG!
比如:
- “数组长度是 3,你非要访问第 100 个元素?”
- “空指针解引用?不存在的!”
- “内存越界?想都别想!”
这些是程序员的 bug,属于“逻辑错误”,程序已经不可信了。
Rust 用 panic!
宏,直接终止程序,防止更严重的后果。
panic!
:程序的“紧急熔断”
想象你家的电路:
- 正常:灯亮、电视开、空调转。
- 突然短路 → 保险丝“啪”地断了,全屋断电。
这不是“系统崩溃”,而是保护机制!
panic!
就是 Rust 的保险丝:
panic!("啊啊啊!程序出大事了!");
运行结果:
thread 'main' panicked at '啊啊啊!程序出大事了!', src/main.rs:4:5
note: run with `RUST_BACKTRACE=1` to display a backtrace
程序立刻停止,打印错误位置,防止数据损坏或安全漏洞。
默认情况下,Rust 还会“展开”(unwind)栈,清理资源。你也可以设置“直接 abort”,更快更狠。
Result<T, E>
:优雅应对“小问题”
这才是 Rust 的核心绝技!比如你想打开一个文件:
use std::fs::File;let f = File::open("hello.txt");
f
的类型是:Result<File, Error>
,它不是直接给你文件,而是说:
“我可能打开成功,也可能失败。你自己判断!”
Result
是个枚举:
enum Result<T, E> {Ok(T), // 成功,返回值 TErr(E), // 失败,返回错误信息 E
}
所以你必须处理两种情况:
match f {Ok(file) => println!("文件打开成功!"),Err(error) => println!("出错了:{}", error),
}
Rust 强制你“面对现实”——不能假装错误不存在!
Result
的神级助手:?
运算符
每次都写 match
太麻烦?Rust 提供了“快捷键”:?
fn read_file() -> Result<String, std::io::Error> {let mut f = File::open("hello.txt")?;let mut s = String::new();f.read_to_string(&mut s)?;Ok(s)
}
?
的意思是:
“如果成功,继续;
如果失败,立刻返回这个错误,别往下走了。”
简洁、安全、优雅!
什么时候用 panic!
?什么时候用 Result
?
Rust 给你选择权,但也有建议:
场景 | 推荐方式 |
---|---|
用户输入错误 | Result (提示重试) |
配置文件缺失 | Result (用默认值) |
数组越界 | panic! (程序员 bug) |
断言失败 | panic! (用 assert! ) |
不可能的情况 | panic! (用 unreachable!() ) |
原则:
- 你能处理? → 用
Result
- 你不能处理,是 bug? → 用
panic!
《Rust 紧急预案》直播现场:当程序“原地爆炸”时,该怎么办?
主持人(你):
“各位观众,欢迎收看《Rust 安全实验室》!今天我们要测试一项黑科技——程序自毁系统!”
测试目标:
panic!
宏
实验口号:“宁可停,不可崩!”
场景一:程序员手滑,直接“引爆炸弹”
fn main() {panic!("不好!我写错代码了!");
}
运行结果:
thread 'main' panicked at '不好!我写错代码了!', src/main.rs:2:5
主持人:
“哇哦!程序当场宣布:‘我出错了!我不干了!’”
这不是崩溃,这是主动辞职!
Rust 说:
“我检测到严重 bug,继续运行可能危害系统——
所以,我选择优雅退出,并留下遗言:‘是这行代码干的!’”
为什么 Rust 不像 C 那样“硬撑”?
打个比方,想象一下,你在玩“大家来找茬”:
- C 语言:你问:“第 100 个元素是啥?”
→ C 回答:“嗯… 是‘彩虹独角兽’!”(其实那块内存是别人的密码!)
这就是缓冲区溢出——黑客最爱的后门!
而 Rust 说:
“第 100 个?你家向量只有 3 个!不许看!” 然后
panic!
,程序终止。
这不是“功能缺失”,而是安全防护!Rust 宁可程序“死”,也不让你的数据“乱”!
场景二:错误不是我写的,但锅得我背?
代码:
let v = vec![1, 2, 3];
v[99]; // 想要第 100 个?
报错:
thread 'main' panicked at 'index out of bounds...', src/main.rs:4:5
主持人:
“咦?报错说是在 src/main.rs:4
,但我没写 panic!
啊?”
真相是:
- 你写的
v[99]
调用了 Rust 内部的“边界检查”函数。 - 那个函数发现你越界了,于是它替你执行了
panic!
。 - 所以错误指向的是你调用它的那一行,非常精准!
如何破案?用“回溯”(Backtrace)!
有时候,错误像一场“连环案”:
你调 A()
→ A 调 B()
→ B 调 C()
→ C 出错了!你只知道 C 炸了,但不知道谁惹的祸。这时候,你需要——犯罪现场回溯录像!
打开“黑匣子”
在终端输入:
RUST_BACKTRACE=1 cargo run
你会看到一串“调用链”:
stack backtrace:0: rust_begin_unwind1: core::panicking::panic_fmt...6: panic::mainat ./src/main.rs:47: core::ops::function::FnOnce::call_once
关键线索:找你写的文件!
比如这行:
6: panic::main at ./src/main.rs:4
说明:问题出在 src/main.rs
第 4 行。
从这里开始查,凶手就在你眼前!
小贴士:
RUST_BACKTRACE=1
→ 看关键路径RUST_BACKTRACE=full
→ 看完整录像(超长)
高级设置:从“优雅关机”到“直接炸毁”
默认情况下,Rust 在 panic!
时会:
- 一层层回溯栈
- 清理每个函数的资源
- 然后退出
这叫“展开”(unwind),像优雅地关电脑。但如果你在乎速度和体积(比如嵌入式设备),可以设置:
# Cargo.toml[profile.release]
panic = 'abort'
意思是:一旦出错,立刻终止,不清理,不回溯,直接“物理删除进程”。
优点:更快、二进制更小
缺点:不优雅(但程序都死了,无所谓)
总结:panic!
使用守则
情况 | 是否该 panic! |
---|---|
数组越界 | 是,Rust 自动触发 |
断言失败 | 用 assert!() |
不可能路径 | 用 unreachable!() |
文件打不开 | 用 Result 处理 |
网络超时 | 提示用户重试 |
用户输错密码 | 让他再输一次 |
记住:
- 是程序员的错? →
panic!
(程序有 bug)- 是用户的错或环境问题? →
Result
(可恢复)
《Rust 生存指南》:别慌,这只是小问题!
主持人:“欢迎来到《Rust 生存指南》!上面我们讲了 panic!
——程序的‘核按钮’:一按就炸,全剧终。”
但现实世界中,程序不是天天爆炸的。更多时候,它只是打了个喷嚏。
比如:
- 文件找不到?
- 网络断了?
- 用户输错密码?
这些都不是“致命伤”,而是“感冒发烧”。这时候,我们不用 panic!
,而是用—— Result<T, E>
:Rust 的“创可贴 + 退烧药”组合包!
Result
是什么?一个“二选一”快递盒!
想象你网购了一件商品,快递到了,但有两种可能:
- 包裹完好 → 里面是你买的“机械键盘”
- 包裹损坏 → 里面是“快递小哥写的道歉信”
在 Rust 里,这就是 Result
:
enum Result<T, E> {Ok(T), // 成功!T 是你想要的东西Err(E), // 失败!E 是错误信息
}
T
:比如文件句柄、字符串、网络连接……E
:比如“文件不存在”、“权限不足”……
Result
就是一个智能快递盒:打开它,要么拿到宝贝,要么看到道歉信。
场景实战:打开一个文件
use std::fs::File;let f = File::open("hello.txt");
这行代码返回一个 Result<File, io::Error>
:
Ok(文件句柄)
→ 文件存在,可以读写Err(错误信息)
→ 比如“文件不存在”或“没权限”
那我们怎么“拆快递”呢?
方法一:match
大法(原始但清晰)
let f = match f {Ok(file) => file, // 成功:拿走文件Err(error) => panic!("出错了:{:?}", error), // 失败:炸!
};
这就像你拆快递:
- 是键盘?→ 开心使用
- 是道歉信?→ 气得把快递站炸了
但……有必要吗?
更聪明的做法:区分错误类型!
不是所有错误都值得“炸楼”!
比如:
- 文件不存在?→ 没事,我帮你新建一个
- 没权限?→ 那真不行,必须 panic
match f {Ok(file) => file,Err(error) => match error.kind() {ErrorKind::NotFound => File::create("hello.txt").unwrap(),_ => panic!("其他错误,没法搞:{:?}", error),},
}
就像修车:
- 没油了?→ 加油
- 发动机炸了?→ 报废
懒人神器:unwrap
和 expect
不想写 match
?Rust 给你两个“快捷按钮”:
1. .unwrap()
- 成功 → 返回值
- 失败 → 自动
panic!
let f = File::open("hello.txt").unwrap();
警告:就像说“如果收不到键盘,我就自爆”——太冲动了!
2. .expect("自定义消息")
更温和的 unwrap
,能留下遗言:
let f = File::open("hello.txt").expect("为啥打不开文件啊?");
报错时你会看到:
thread 'main' panicked at '为啥打不开文件啊?: NotFound'
推荐:至少告诉别人你为什么以为它不会失败
高级技巧:错误“甩锅”术——?
运算符
有时候,你不想自己处理错误,只想说:
“这锅我不背,交给上一级!”这就是
?
运算符的使命!
fn read_username() -> Result<String, io::Error> {let mut f = File::open("hello.txt")?; // 打开失败?→ 直接甩锅!let mut s = String::new();f.read_to_string(&mut s)?; // 读取失败?→ 再甩锅!Ok(s)
}
?
的行为:
-
Ok
→ 继续执行 -
Err
→ 立刻返回错误,函数提前结束
就像流水线工人:
- 上游送来合格零件 → 继续加工
- 上游送来废品 → 直接扔给主管处理!
?
的隐藏技能:自动“错误转换”
?
不只是甩锅,它还会帮你把错误“翻译”成统一格式。
比如:
- A 函数返回
ParseError
- B 函数返回
IoError
- 但你的函数只想返回
AppError
?
会自动调用 From
trait 把不同错误转成 AppError
。
就像公司综合部:所有部门的报告,统一转成“总经理看得懂”的版本。
但是,请注意!?
不能乱用!
?
只能在返回 Result
或 Option
的函数里用。比如,main
函数默认返回 ()
(空元组),你不能直接用 ?
:
fn main() {let f = File::open("hello.txt")?;
}
解法一:改 main
的返回值
fn main() -> Result<(), Box<dyn std::error::Error>> {let f = File::open("hello.txt")?;Ok(())
}
Box<dyn Error>
:意思是“任何类型的错误我都接”Ok(())
:成功时返回“空元组”
这样,?
就可以愉快地甩锅了!
解法二:老老实实用 match
或 .expect()
总结:Result
使用口诀
场景 | 推荐做法 |
---|---|
快速原型、测试 | .expect("说明") |
确信不会失败 | .unwrap() (但要小心!) |
想甩锅给调用者 | ? 运算符 |
需要自定义处理 | match 表达式 |
统一错误类型 | 实现 From trait |
最后灵魂拷问:panic!
还是 Result
?
问题类型 | 用哪个? | 例子 |
---|---|---|
程序有 bug | panic! | 数组越界、解引用 None |
环境问题 | Result | 文件不存在、网络超时 |
用户操作错误 | Result | 密码错误、输入格式不对 |
记住:
panic!
是“程序自杀”Result
是“程序说:我尽力了,但你需要处理一下”