11-11.【错误处理】什么是 Typed Error?与普通 Error 的差异和优势?

1 阅读3分钟

Typed Error(类型化错误) 是 Swift 6.0 引入的一项重大语言特性。它允许开发者在函数声明中明确指定可能抛出的错误类型,而不仅仅是模糊的 throws

在此之前,Swift 的所有错误在底层都会被“装箱”成 any Error(存在体类型),这类似于一种类型擦除。


1. 语法差异:从“模糊”到“精准”

普通 Error (Untyped)

在 Swift 5.9 及以前,你只能知道一个函数“会报错”,但不知道会报“什么错”。

Swift

func fetchData() throws -> Data { 
    // 编译器只知道这里会抛出 any Error
}

// 调用方:必须写一个 catch all 分支,或者处理 any Error
do {
    try fetchData()
} catch {
    // 这里的 error 类型是 any Error,需要手动转型
    if let networkError = error as? NetworkError { ... }
}

Typed Error (Swift 6.0)

现在你可以通过 throws(ErrorType) 明确指明错误契约。

Swift

func fetchData() throws(NetworkError) -> Data { 
    // 编译器强制要求只能抛出 NetworkError 或其子类
}

// 调用方:编译器知道 error 确切就是 NetworkError
do {
    try fetchData()
} catch {
    // 这里的 error 自动推断为 NetworkError 类型,无需 as? 转换
    switch error {
    case .timeout: ...
    case .noConnection: ...
    }
}

2. 核心优势:为什么我们需要它?

A. 增强防御性:编译器级别的契约

在普通 throws 中,如果你在函数里不小心抛出了一个逻辑之外的错误,编译器不会拦你。

而在 Typed Error 中,如果你声明了 throws(MyError) 却尝试抛出 OtherError编译会报错。这确保了 API 的行为与文档高度一致。

B. 性能飞跃:消除“装箱”开销

这是底层实现上的最大优势。

  • 普通 Error:抛出错误时,系统需要在堆(Heap)上分配内存来包装错误对象,并处理引用计数。

  • Typed Error:由于编译器确切知道错误的大小,它可以直接通过寄存器或栈传递错误值,完全避免了动态内存分配。

    在嵌入式 Swift (Embedded Swift) 等对内存极其敏感的场景下,这是唯一可用的错误处理方案。

C. 完美的泛型支持

在编写高级函数(如 mapflatMap)时,Typed Error 允许错误类型进行“透传”。


3. 性能与内存模型对比

特性普通 throwsTyped throws(E)
错误类型any Error (运行时确定)E (编译时确定)
内存分配涉及堆分配 (Boxing)零堆分配 (Inline/Register)
** catch 逻辑**需要 as? 类型转换自动推断,直接匹配
ABI 稳定性适合高度抽象的库适合性能敏感或严格契约的模块

4. 什么时候不应该使用 Typed Error?

虽然 Typed Error 看起来很棒,但它并不是要完全取代普通 throws

  • 过度耦合:如果你在公共 API 中过度使用具体的错误类型,未来如果你想增加一种新的错误类别,可能会破坏调用方的代码。

  • 推荐做法

    • 使用 Typed Error:在模块内部、底层库、性能敏感的组件、或者错误类型非常固定的场景(如 Result 的桥接)。
    • 使用普通 Error:在复杂的业务层、大型插件系统的接口处,保持灵活性。

总结:防御式编程的新武器

Typed Error 让 Swift 的错误处理从“运行时检查”进化到了“编译时保证”。它结合了 Result<T, E>类型安全性throws 语法的简洁性