失败处理

让我们回顾一下上一练习中的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

OkErr都是泛型,允许你为成功和错误情况指定自己的类型。

无异常

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