Skip to content
📖 Auto 版 Rust Book本书目录

错误处理 ​

错误是软件中不可避免的一部分。Auto 提供了两种类型级别的机制来处理出错的情况,灵感来自 Rust 的方法,但语法更加简洁:

  • ?T 表示一个可能不存在的值 —— 等价于 Rust 的 Option<T>。它有两个变体:Some(T) 和 None。
  • !T 表示一个可能失败的操作 —— 等价于 Rust 的 Result<T, E>(其中 E 始终是 String)。它有两个变体:Ok(T) 和 Err(String)。

与 Rust 一样,Auto 要求你承认错误的可能性并在代码编译前采取行动。这一要求使你的程序更加健壮,确保你能在部署到生产环境之前发现并适当处理错误。

我们将首先介绍用 ?T 处理可恢复错误,然后讨论用 !T 进行错误传播,最后探讨何时该 panic、何时该恢复。

用 ?T(Option)处理可恢复错误 ​

大多数错误并不严重到需要程序完全停止的程度。有时一个函数可能没有值可返回 —— 例如,在列表中查找一个不存在的项,或查找一个不存在的字符的索引。在这些情况下,?T 是合适的工具。

?T 类型有两个变体:

  • Some(value) —— 操作成功并产生了 value
  • None —— 操作失败或没有产生值

从函数返回 ?T ​

当一个函数可能没有值可返回时,将其返回类型声明为 ?T:

返回 `?int` 的函数和 `??` 空值合并运算符

?? 运算符(空值合并)是 Auto 提供的优雅方式,用于在回退值的情况下解包可选值。它替代了 Rust 中冗长的 .unwrap_or(default) 模式。如果值是 Some(v),则返回 v;如果是 None,则返回默认值。

用 is 对 ?T 进行模式匹配 ​

当你需要对 Some 和 None 采取不同的行为时,使用 is 表达式:

用 `is` 对 `?int` 进行模式匹配

is 表达式是 Auto 的模式匹配语法(替代 Rust 的 match)。它解构 ?T 值:None 匹配没有值的情况,Some(v) 将内部值绑定到 v。

?T 与 Option<T> 速查 ​

AutoRust含义
?intOption<i32>一个可能不存在的整数
Some(42)Some(42)值存在
NoneNone无值
x ?? 0x.unwrap_or(0)带默认值解包
x is { None => ... }match x { None => ... }模式匹配
v[i] ?? defaultv.get(i).copied().unwrap_or(default)安全列表访问

用 !T(Result)和错误传播处理不可恢复错误 ​

对于可能以错误消息失败的操作(而不仅仅是"无值"),Auto 使用 !T。这等价于 Rust 的 Result<T, E>,其中 E 是 String。

!T 类型有两个变体:

  • Ok(value) —— 操作成功
  • Err(message) —— 操作失败,带有字符串错误消息

用 .collect() 提取值 ​

当你确信操作应该成功时,使用 .collect()(转译为 Rust 的 unwrap())从 !T 中提取值。如果实际值是 Err,程序将 panic:

用 `.collect()` 从 `!T` 中提取值

这里,text.to_int() 返回 !int(Result)。.collect() 运算符提取内部值。如果解析失败,程序会带着错误消息 panic。

用 .collect() 和 fn main() ! 进行错误传播 ​

当你希望错误向上传播而不是内联处理时,用 ! 声明函数返回类型,并使用 .collect() 来传播错误:

用 `fn main() !` 和 `.collect()` 进行错误传播

fn main() ! 语法告诉 Auto main 可以返回错误。这等价于 Rust 的 fn main() -> Result<(), String>。当函数返回 Ok 时,程序以代码 0 退出。当返回 Err 时,错误消息会被打印,程序以非零代码退出。

与 Rust 的关键区别:在 Rust 中,你使用 ? 运算符进行错误传播。在 Auto 中,当错误类型匹配时,.collect()(写为 .!)承担了这个角色。

!T 与 Result<T, E> 速查 ​

AutoRust含义
!intResult<i32, String>一个整数或错误消息
Ok(42)Ok(42)成功值
Err("msg")Err("msg".to_string())错误
x.!x?传播错误 / 解包
fn foo() !intfn foo() -> Result<i32, String>可返回错误的函数
fn main() !fn main() -> Result<(), String>可返回错误的 main

在函数中使用 None 和 Some ​

返回 ?T 的函数使用 None 表示"无值",使用 Some(value) 表示成功结果。这与 Rust 的 Option 相同:

使用 `None` 和 `Some` 作为可选返回值

这个函数在除以零时返回 None(一个不可能的操作),在除法成功时返回 Some(result)。调用者必须处理这两种情况。

使用 .expect() 处理不可恢复的错误 ​

当你确信操作应该成功,并希望在失败时用描述性消息 panic 时,使用 .expect():

使用 `.expect()` 处理不可恢复的错误

.expect("message") 方法的工作方式与 Rust 中完全相同:如果是 Ok/Some,则返回内部值;如果是 Err/None,则带着提供的消息 panic。

在生产代码中,优先使用 .expect() 而不是 .collect() —— 消息能让调试更轻松。

要 panic 还是不 panic ​

Auto 在错误处理决策上遵循与 Rust 相同的理念:

使用 ?T(Option)当:

  • 值可能自然不存在(例如,搜索列表、查找键)
  • 调用者应该决定没有值时怎么做
  • 缺失值是预期的,而非异常情况

使用 !T(Result)当:

  • 操作可能以有意义的错误消息失败(例如,解析、I/O)
  • 调用者需要知道失败的原因
  • 错误可能在更高层级被恢复

使用 .expect() 或 .collect() 当:

  • 失败表明代码中存在 bug
  • 你拥有比编译器更多的信息(例如,一个硬编码的有效值)
  • 在测试和原型中,错误处理会模糊逻辑

使用 fn main() ! 当:

  • 你的顶层代码执行可能失败的操作
  • 你希望错误传播到程序退出,而不是被静默忽略
  • 你更偏好清晰的错误消息而非崩溃诊断

错误处理指南 ​

当你的代码可能进入不良状态时,建议使用 panic(通过 .expect() 或在失败值上使用 .collect()):

  • 不良状态是意外的,而非偶尔可能发生的(比如用户输入无效数据)。
  • 后续代码依赖于不处于这种不良状态。
  • 没有好的方式将这些信息编码到你使用的类型中。

当失败是预期的时,返回 ?T 或 !T 比 panic 更合适。例如:解析器收到格式错误的数据,或用户输入验证检查失败。

在生产质量的代码中,大多数 Auto 程序员选择 .expect("原因") 而不是裸的 .collect(),以提供更多关于为什么操作预期会成功的上下文。这样,如果假设被证明是错误的,你有更多信息可用于调试。

总结 ​

Auto 的错误处理建立在两个直接映射到 Rust 错误类型的类型级构造上:

概念AutoRust
可空值?TOption<T>
错误结果!TResult<T, E>
解包或默认值x ?? defaultx.unwrap_or(default)
传播错误x.!x?
带消息 panic.expect("msg").expect("msg")
可返回错误的 mainfn main() !fn main() -> Result<(), String>
模式匹配x is { ... }match x { ... }

Auto 相对于 Rust 的主要改进:

  1. 更简洁的类型语法:?int 替代 Option<i32>,!int 替代 Result<i32, String>。
  2. 空值合并:x ?? 0 替代冗长的 x.unwrap_or(0)。
  3. 一致的错误类型:!T 始终使用 String 作为错误类型,消除了泛型错误类型的复杂性。
  4. 模式匹配:is 替代 match,语法更清晰、更直观。

在下一章中,我们将讨论泛型类型以及如何定义自己的带有 ?T 或 !T 参数的类型。

基于 MIT 许可发布。