失败处理
让我们回顾一下上一练习中的Ticket::new
函数:
#![allow(unused)] fn main() { impl Ticket { pub fn new(title: String, description: String, status: Status) -> Ticket { // ... 检查逻辑和错误处理 ... Ticket { title, description, status, } } } }
一旦检查失败,该函数就会恐慌。这并不理想,因为它没有给调用者处理错误的机会。
现在是时候介绍Result
类型了,这是Rust处理错误的主要机制。
Result
类型
Result
类型是标准库中定义的一个枚举:
#![allow(unused)] fn main() { enum Result<T, E> { Ok(T), Err(E), } }
它有两个变体:
Ok(T)
:表示成功执行的操作,包含操作的输出T
。Err(E)
:表示失败的操作,包含发生的错误E
。
Ok
和Err
都是泛型,允许你为成功和错误情况指定自己的类型。
无异常
Rust中的可恢复错误以值的形式表示。它们只是一个类型的实例,像任何其他值一样被传递和操作。这与其他语言(如Python或C#)使用异常来指示错误有显著不同。
异常创建了一个难以推理的独立控制流路径。仅凭函数签名,你无法知道它是否会抛出异常。仅凭函数签名,你也无法知道它会抛出哪种异常类型。你必须阅读函数的文档或查看其实现才能了解。
异常处理逻辑的局部性很差:抛出异常的代码与捕获它的代码相距甚远,两者之间没有直接联系。
失败处理编码在类型系统中
Rust通过Result
强制你在函数签名中编码失败的可能性。如果一个函数可能失败(并且你想让调用者有机会处理错误),它必须返回一个Result
。
#![allow(unused)] fn main() { // 仅凭签名,你就能知道这个函数可能会失败。 // 你还可以检查`ParseIntError`以了解可能出现的失败类型。 fn parse_int(s: &str) -> Result<i32, ParseIntError> { // ... } }
这就是Result
的最大优势:它使失败显式化。
但是,请记住,恐慌依然存在。它们没有被类型系统跟踪,就像其他语言中的异常一样。但它们用于不可恢复的错误,应谨慎使用。
参考资料
- 本节练习位于
exercises/05_ticket_v2/06_fallibility