🎓 A1Learner
🔍
0 of 27 lessons completed

Swift Rethrows

Rethrows is a marker for a higher-order function that throws only when the closure passed to it throws. The same function can be called with or without try, depending on that closure.

A non-throwing closure needs no try

The closure only doubles each number, so it cannot throw. The call to the rethrows function therefore needs no try at all.

func transformAll(_ numbers: [Int], using change: (Int) throws -> Int) rethrows -> [Int] {
    var result: [Int] = []
    for number in numbers {
        result.append(try change(number))
    }
    return result
}
let doubled = transformAll([1, 2, 3]) { $0 * 2 }
print(doubled)

A throwing closure is caught by the caller

This closure throws when it meets a negative number. The call now needs try, and the catch block prints its line instead of a result.

enum LimitError: Error {
    case negative
}
func transformAll(_ numbers: [Int], using change: (Int) throws -> Int) rethrows -> [Int] {
    var result: [Int] = []
    for number in numbers {
        result.append(try change(number))
    }
    return result
}
do {
    let result = try transformAll([2, -1, 3]) { value in
        if value < 0 {
            throw LimitError.negative
        }
        return value * 10
    }
    print(result)
} catch {
    print("Stopped by a negative value")
}

One function, both call styles

The first call passes a closure that only adds one, so it runs without try. The second call passes a throwing closure and runs inside a do block.

enum LimitError: Error {
    case negative
}
func transformAll(_ numbers: [Int], using change: (Int) throws -> Int) rethrows -> [Int] {
    var result: [Int] = []
    for number in numbers {
        result.append(try change(number))
    }
    return result
}
let raised = transformAll([5, 6]) { $0 + 1 }
print(raised)
do {
    let result = try transformAll([4, -2]) { value in
        if value < 0 {
            throw LimitError.negative
        }
        return value + 1
    }
    print(result)
} catch {
    print("Stopped by a negative value")
}

A rethrows function that runs work

The run function prints a label and then calls the closure it was given. Plain work runs directly, and throwing work is wrapped in a do block.

enum JobError: Error {
    case failed
}
func run(_ label: String, using work: () throws -> Void) rethrows {
    print(label)
    try work()
}
run("Start") {
    print("Working")
}
do {
    try run("Retry") {
        throw JobError.failed
    }
} catch {
    print("Job failed")
}

Example

enum LimitError: Error {
    case negative
}
func transformAll(_ numbers: [Int], using change: (Int) throws -> Int) rethrows -> [Int] {
    var result: [Int] = []
    for number in numbers {
        result.append(try change(number))
    }
    return result
}
let tripled = transformAll([4, 5]) { $0 * 3 }
print(tripled)
do {
    let result = try transformAll([7, -2]) { value in
        if value < 0 {
            throw LimitError.negative
        }
        return value + 1
    }
    print(result)
} catch {
    print("Stopped by a negative value")
}

Key points

  • rethrows marks a higher-order function that can throw only through its closure parameter.
  • The function itself throws no error of its own.
  • A call whose closure never throws needs no try and no do block.
  • A call whose closure can throw needs try like any other throwing call.
  • The error that reaches the caller is the error the closure threw.

In this example, the first call triples 4 and 5 with a closure that cannot throw, so the call has no try and prints the list 12 and 15. The second call passes a closure that throws on the negative value. That call sits inside a do block with try. The catch block prints the stop line and no second list appears.

Note

The word rethrows takes the place of throws in the function declaration.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

A rethrows function is called with a closure that never throws. What does that call need?