What are the differences between throws and rethrows in Swift?

20,736

Solution 1

From "Declarations" in the Swift book:

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.

A typical example is the map method:

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

If map is called with a non-throwing transform, it does not throw an error itself and can be called without try:

// Example 1:

let a = [1, 2, 3]

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

let a1 = a.map(f1)

But if map is called with a throwing closure then itself can throw and must be called with 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 {
    // ...
}
  • If map were declared as throws instead of rethrows then you would have to call it with try even in example 1, which is "inconvenient" and bloats the code unnecessary.
  • If map were declared without throws/rethrows then you could not call it with a throwing closure as in example 2.

The same is true for other methods from the Swift Standard Library which take function parameters: filter(), index(where:), forEach() and many many more.

In your case,

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

denotes a function which can throw an error, even if called with a non-throwing argument, whereas

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

denotes a function which throws an error only if called with a throwing argument.

Roughly speaking, rethrows is for functions which do not throw errors "on their own", but only "forward" errors from their function parameters.

Solution 2

Just to add something along with Martin's answer. A non throwing function with the same signature as a throwing function is considered a sub-type of the throwing function. That is why rethrows can determine which one it is and only require try when the func param also throws, but still accepts the same function signature that doesn't throw. It's a convenient way to only have to use a do try block when the func param throws, but the other code in the function doesn't throw an error.

Share:
20,736
Ahmad F
Author by

Ahmad F

I appreciate your kind comments/suggestions and edits to my posts. Feel free to use any code in my posts without attribution (that's what usually happens anyway πŸ˜…). I don't usually downvote, but when I do, I mention the reason. It would be nice if you check my stories ✍️ My "humble" todo list so far: βœ… Swift tag: Silver Badge. βœ… iOS tag: Silver Badge. βœ… 10k Reputation. βœ… Swift tag: Gold Badge. βœ… 15k Reputation. βœ… iOS tag: Gold Badge. βœ… IMPACT ~1m people reached (hopefully, being useful to good amount of people). βœ… 10 Gold Badges. πŸ”« 25k Reputation (getting all privileges). Oh boy it could go on forever! but I am satisfied with it so far... Highest 100 users reputation located at: πŸ˜€ Jordan 😐 Arabs Users I follow:

Updated on July 08, 2022

Comments

  • Ahmad F
    Ahmad F almost 2 years

    After searching for some references to figure it out, -unfortunately- I could not find useful -and simple- description about understanding the differences between throws and rethrows. It is kind of confusing when try to understand how we should use them.

    I would mention that I am kind of familiar with the -default- throws with its simplest form for propagating an error, as follows:

    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")
        }
    }
    

    So far so good, but the problem arises when:

    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
    }
    

    what I know so far is when calling a function that throws it has to be handled by a try, unlike the rethrows. So what?! What is logic that we should follow when deciding to use throws or rethrows?

  • Klaas
    Klaas over 6 years
    The last sentence is golden!
  • Martin R
    Martin R over 6 years
    @Honey: The last sentence in the answer is how I would sum it up.
  • mfaani
    mfaani over 6 years
    Yeah that seems better. Would it be correct to say rethrows are only used with closures, other than that they aren't needed?
  • Martin R
    Martin R over 6 years
    @Honey: I do not completely get what you mean. rethrows is only used with functions which take function parameters that might throw.
  • Kautsya Kanu
    Kautsya Kanu over 3 years
    How syntax would be if it do both?? πŸ€”πŸ€” Would it be "rethrows throws" ??
  • Martin R
    Martin R over 3 years
    @KautsyaKanu: Only throws .