Swift 中的 throws 和 rethrows 有什么区别?

What are the differences between throws and rethrows in Swift?

在搜索了一些参考资料来弄清楚之后,-不幸的是-我找不到有用且简单的描述来理解 throwsrethrows 之间的差异。当试图理解我们应该如何使用它们时,这有点令人困惑。

我想提一下,我有点熟悉 -default- throws 及其传播错误的最简单形式,如下所示:

enum CustomError: Error {
    case potato
    case tomato
}

func throwCustomError(_ string: String) throws {
    if string.lowercased().trimmingCharacters(in: .whitespaces) == "potato" {
        throw CustomError.potato
    }

    if string.lowercased().trimmingCharacters(in: .whitespaces) == "tomato" {
        throw CustomError.tomato
    }
}

do {
    try throwCustomError("potato")
} catch let error as CustomError {
    switch error {
    case .potato:
        print("potatos catched") // potatos catched
    case .tomato:
        print("tomato catched")
    }
}

到目前为止一切顺利,但问题出现在:

func throwCustomError(function:(String) throws -> ()) throws {
    try function("throws string")
}

func rethrowCustomError(function:(String) throws -> ()) rethrows {
    try function("rethrows string")
}

rethrowCustomError { string in
    print(string) // rethrows string
}

try throwCustomError { string in
    print(string) // throws string
}

到目前为止,我所知道的是,当调用 throws 函数时,它必须由 try 处理,这与 rethrows 不同。所以呢?!我们在决定使用throwsrethrows时应该遵循什么逻辑?

来自 Swift 书中的 "Declarations"

Rethrowing Functions and Methods

A function or method can be declared with the rethrows keyword to indicate that it throws an error only if one of it’s function parameters throws an error. These functions and methods are known as rethrowing functions and rethrowing methods. Rethrowing functions and methods must have at least one throwing function parameter.

一个典型的例子是map方法:

public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

如果使用非抛出转换调用 map,它不会抛出 一个错误本身,可以在没有 try:

的情况下调用
// Example 1:

let a = [1, 2, 3]

func f1(n: Int) -> Int {
    return n * n
}

let a1 = a.map(f1)

但是如果 map 被调用时带有抛出闭包那么它自己可以抛出 并且必须用 try:

调用
// Example 2:

let a = [1, 2, 3]
enum CustomError: Error {
    case illegalArgument
}

func f2(n: Int) throws -> Int {
    guard n >= 0 else {
        throw CustomError.illegalArgument
    }
    return n*n
}


do {
    let a2 = try a.map(f2)
} catch {
    // ...
}
  • 如果 map 被声明为 throws 而不是 rethrows 那么你会 即使在示例 1 中也必须使用 try 来调用它, 这是 "inconvenient" 并且使代码变得不必要。
  • 如果在没有 throws/rethrows 的情况下声明 map 那么你不能 像示例 2 一样使用抛出闭包调用它。

Swift标准库中的其他方法也是如此 它采用函数参数:filter()index(where:)forEach() 等等。

在你的情况下,

func throwCustomError(function:(String) throws -> ()) throws

表示一个函数可能会抛出错误,即使使用 一个非抛出参数,而

func rethrowCustomError(function:(String) throws -> ()) rethrows

表示一个函数只有在调用时才会抛出错误 抛出参数。

粗略地说,rethrows 用于不抛出异常的函数 错误 "on their own",但函数只有 "forward" 个错误 参数。

只是为了补充 Martin 的回答。与抛出函数具有相同签名的非抛出函数被认为是抛出函数的sub-type。这就是为什么 rethrows 可以确定它是哪一个并且只需要 try 当 func 参数也抛出时,但仍然接受不抛出的相同函数签名。当 func 参数抛出时,只需要使用 do try 块是一种方便的方法,但函数中的其他代码不会抛出错误。