作为前端,我们有两个“一生之敌”:
Uncaught TypeError: Cannot read properties of undefined(访问了空值)try { ... } catch (e) { ... }(面条式错误处理)
Rust 说:我们不需要 null,也不需要 Exception (异常)。 Rust 用两个普通的 枚举 (Enum) 解决了这两个问题:Option 和 Result。
1. Option —— 薛定谔的盒子
在 Rust 里,变量永远不可能是 null。如果你想要一个“可能为空”的值,你必须把它装进 Option 盒子。
Option 枚举长这样(标准库内置):
1 | |
实战对比:
JS (TypeScript):
1 | |
Rust:
1 | |
Rust 强迫你在使用数据之前,必须先检查盒子是不是空的。这就从根源上消灭了 undefined 报错。
2. Result<T, E> —— 成功还是失败?
对于可能报错的操作(比如读取文件、网络请求),Rust 使用 Result。它也是个枚举:
1 | |
这比 try-catch 清晰得多,因为错误处理变成了正常的逻辑流程,而不是“异常跳转”。
一个最受 Rust 开发者喜爱的符号:?
如果每一个 Result 都要写 match 来处理,代码会很啰嗦。Rust 提供了一个神级语法糖:问号操作符 (?)。
它的意思是:“如果是 Error,直接把错误抛给上一层(return Err);如果是 Ok,把里面的数据取出来给我。”
这非常像 JS 中的 await,能把异步代码写成同步的样子;? 能把错误处理代码写成线性的样子。
1 | |
看到那些 ? 了吗?它帮我们省去了无数的 if err != nil 或者 try-catch。
🧠 随堂小测验
假设我们要写一个除法函数 divide。 大家都知道,除数不能为 0。如果用户传了 0,我们应该报错。
请从下面三个选项中,选出最符合 Rust 风格的函数签名(Function Signature):
A. fn divide(a: i32, b: i32) -> i32 (如果除以0,让程序崩溃 panic)
B. fn divide(a: i32, b: i32) -> Option<i32> (如果除以0,返回 None;否则返回 Some结果)
C. fn divide(a: i32, b: i32) -> Result<i32, String> (如果除以0,返回 Err(“不能除以0”);否则返回 Ok结果)
(提示:B 和 C 都是合法的 Rust 风格,但如果是为了告诉调用者为什么失败,哪个更好?)
🎉 C 是最佳答案。
为什么选 C (Result) 而不是 B (Option)?
虽然用 Option (返回 None) 来表示“除以零”也是合法的,但 Result 提供了一个关键的优势:它可以携带错误信息。
Option<T>:适用于“有”或“没有”的场景(比如查找数组里的元素,没找到就是None,不需要解释为什么)。Result<T, E>:适用于“成功”或“失败”的场景,特别是当你需要告诉调用者为什么失败时(比如“除数不能为零”、“文件不存在”、“权限不足”)。
这样调用者不仅知道出错了,还能把错误信息打印日志或者展示给用户。
我们已经攻克了 Rust 的“三大难关”:所有权、借用、生命周期(其实在借用里我们隐式地学到了,不用太担心),以及刚刚搞定的错误处理。
现在,我们要进入 第三阶段:抽象与并发。这一阶段会让你的代码变得真正“高级”起来。