Bird
Raised Fist0
Swiftprogramming~15 mins

Rethrowing functions in Swift - Deep Dive

Choose your learning style10 modes available
Overview - Rethrowing functions
What is it?
Rethrowing functions in Swift are special functions that can throw errors only if the functions they receive as parameters throw errors. They do not throw errors on their own but pass along errors from the functions they call. This helps write flexible code that can handle errors from different sources without adding extra error handling inside the rethrowing function.
Why it matters
Without rethrowing functions, you would have to catch and handle errors inside every function that calls a throwing function, even if you just want to pass the error up. This would make code more complex and less reusable. Rethrowing functions let you write cleaner, more readable code that only throws errors when necessary, improving safety and clarity.
Where it fits
Before learning rethrowing functions, you should understand basic Swift functions, error handling with throw and try, and how to write throwing functions. After mastering rethrowing functions, you can explore advanced error propagation patterns, custom error types, and asynchronous error handling in Swift.
Mental Model
Core Idea
A rethrowing function only throws errors if the function it calls throws, passing errors along without adding new ones.
Think of it like...
Imagine a mail carrier who only delivers letters that contain complaints. If the letter has no complaint, the carrier just delivers it silently. The carrier never creates complaints but passes them along if they exist.
┌─────────────────────────────┐
│ Rethrowing Function (rethrows) │
├──────────────┬──────────────┤
│ Calls passed │ Calls passed │
│ throwing    │ throwing    │
│ function    │ function    │
│ (may throw) │ (may throw) │
└───────┬─────┴───────┬───────┘
        │             │
        ▼             ▼
   Throws error   Does not throw
   if called fn   if called fn
   throws error   does not throw
Build-Up - 7 Steps
1
FoundationUnderstanding throwing functions
🤔
Concept: Learn what throwing functions are and how they signal errors in Swift.
In Swift, a function can be marked with 'throws' to indicate it might produce an error. When calling such a function, you use 'try' to handle the possibility of an error. For example: func mightFail() throws { // code that might throw an error } try mightFail() // call with try If an error occurs, it must be caught or propagated.
Result
You understand how to write and call functions that can throw errors.
Knowing how throwing functions work is essential because rethrowing functions build on this concept to pass errors along.
2
FoundationBasic error handling with try and catch
🤔
Concept: Learn how to catch and handle errors thrown by functions.
You can handle errors using do-catch blocks: do { try mightFail() } catch { print("Caught error: \(error)") } This lets you respond to errors gracefully instead of crashing.
Result
You can safely call throwing functions and handle their errors.
Understanding error handling prepares you to see how errors can be passed through functions without catching them immediately.
3
IntermediateIntroducing rethrowing functions
🤔Before reading on: do you think a rethrowing function can throw errors even if the passed function does not? Commit to your answer.
Concept: Learn how to declare functions that only throw if the functions they call throw, using the 'rethrows' keyword.
A rethrowing function is declared with 'rethrows' instead of 'throws'. It can only throw errors if one of its function parameters throws. For example: func perform(action: () throws -> Void) rethrows { try action() } If 'action' throws, 'perform' throws too. If 'action' does not throw, 'perform' does not throw.
Result
You can write functions that conditionally throw errors based on their parameters.
Understanding that 'rethrows' limits throwing to only when called functions throw helps write safer, more flexible APIs.
4
IntermediateCalling rethrowing functions safely
🤔Before reading on: if you call a rethrowing function with a non-throwing closure, do you need to use 'try'? Commit to your answer.
Concept: Learn how to call rethrowing functions with throwing and non-throwing closures and when 'try' is required.
When you call a rethrowing function: - If you pass a throwing closure, you must use 'try' because errors might be thrown. - If you pass a non-throwing closure, you do not need 'try' because no errors can be thrown. Example: func perform(action: () throws -> Void) rethrows { try action() } // Non-throwing closure call perform { print("No error") } // Throwing closure call try perform { throw NSError(domain: "Error", code: 1, userInfo: nil) }
Result
You know when to use 'try' with rethrowing functions depending on the closure passed.
Knowing this prevents unnecessary error handling and keeps code clean.
5
IntermediateMultiple throwing parameters with rethrows
🤔
Concept: Learn how rethrowing works when a function has multiple throwing function parameters.
If a rethrowing function has multiple function parameters that can throw, it will throw if any of those functions throw. For example: func combine( first: () throws -> Void, second: () throws -> Void ) rethrows { try first() try second() } If either 'first' or 'second' throws, 'combine' throws.
Result
You understand how errors propagate through multiple throwing parameters in rethrowing functions.
This helps design complex functions that delegate error throwing to multiple sources without adding their own errors.
6
AdvancedLimitations of rethrows in Swift
🤔Before reading on: can a rethrowing function throw errors from code inside it that is not a function parameter? Commit to your answer.
Concept: Understand that rethrowing functions cannot throw errors from their own code, only from called throwing parameters.
A rethrowing function cannot throw errors from its own internal code. If it needs to throw errors itself, it must be marked 'throws' instead of 'rethrows'. For example, this is invalid: func invalid() rethrows { throw NSError(domain: "Error", code: 1, userInfo: nil) // Error: cannot throw } You must use 'throws' if you want to throw errors directly.
Result
You know the strict rule that rethrowing functions only throw errors from parameters, not from their own code.
Understanding this prevents misuse of 'rethrows' and clarifies when to use 'throws' instead.
7
ExpertCompiler checks and optimization with rethrows
🤔Before reading on: do you think the Swift compiler can optimize calls to rethrowing functions when passed non-throwing closures? Commit to your answer.
Concept: Learn how the Swift compiler uses 'rethrows' to enforce error propagation rules and optimize code paths.
The Swift compiler enforces that rethrowing functions only throw if their parameters throw. This allows the compiler to: - Prevent unnecessary error handling when non-throwing closures are passed. - Optimize generated code by skipping error handling paths when safe. This means using 'rethrows' improves both safety and performance in Swift programs.
Result
You understand the compiler's role in enforcing and optimizing rethrowing functions.
Knowing this explains why 'rethrows' exists beyond syntax—it enables safer and faster code.
Under the Hood
At runtime, a rethrowing function calls the function parameter using 'try'. If the called function throws an error, the error propagates up through the rethrowing function to its caller. The rethrowing function itself does not generate or catch errors internally. The Swift compiler tracks which functions can throw and enforces that rethrowing functions only throw when their parameters throw, ensuring type safety and predictable error flow.
Why designed this way?
Swift introduced 'rethrows' to solve the problem of writing higher-order functions that call throwing functions without forcing all callers to handle errors unnecessarily. Before 'rethrows', functions that called throwing functions had to be marked 'throws' themselves, even if they didn't throw errors directly. This led to verbose and less clear code. 'Rethrows' provides a precise way to propagate errors only when needed, improving code clarity and safety.
┌─────────────────────────────┐
│ Caller                     │
│  │                        │
│  ▼                        │
│ Rethrowing Function (rethrows) │
│  │                        │
│  ▼                        │
│ Called Function (throws or not)│
│  │                        │
│  ▼                        │
│ Error thrown? ── Yes ──> Propagate error up
│               └─ No ──> Continue normally
└─────────────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does a rethrowing function always throw errors regardless of its parameters? Commit to yes or no.
Common Belief:A rethrowing function always throws errors just like a throwing function.
Tap to reveal reality
Reality:A rethrowing function only throws errors if the function it calls throws an error. If the called function does not throw, the rethrowing function does not throw either.
Why it matters:Believing this causes unnecessary error handling and 'try' usage, making code more complex and less readable.
Quick: Can a rethrowing function throw errors from its own code inside the function body? Commit to yes or no.
Common Belief:A rethrowing function can throw errors from its own code just like a throwing function.
Tap to reveal reality
Reality:A rethrowing function cannot throw errors from its own code. It can only throw errors from the functions it calls that are passed as parameters.
Why it matters:Misunderstanding this leads to compiler errors and confusion about when to use 'throws' versus 'rethrows'.
Quick: If you pass a non-throwing closure to a rethrowing function, do you need to use 'try' when calling it? Commit to yes or no.
Common Belief:You always need to use 'try' when calling a rethrowing function, regardless of the closure passed.
Tap to reveal reality
Reality:You only need to use 'try' if the closure passed to the rethrowing function can throw. If the closure is non-throwing, 'try' is not required.
Why it matters:This misconception leads to unnecessary 'try' usage and cluttered code.
Quick: Does 'rethrows' allow a function to throw new errors unrelated to its parameters? Commit to yes or no.
Common Belief:'Rethrows' lets a function throw any error it wants, not just those from parameters.
Tap to reveal reality
Reality:'Rethrows' restricts throwing to only errors from the function parameters. It cannot throw new errors on its own.
Why it matters:Misusing 'rethrows' can cause unexpected compiler errors and incorrect error handling.
Expert Zone
1
Rethrowing functions enable writing generic higher-order functions that adapt error handling based on passed closures, improving API flexibility.
2
The compiler's static analysis of 'rethrows' allows it to optimize away error handling code paths when non-throwing closures are used, enhancing performance.
3
Using 'rethrows' correctly helps maintain clear error propagation chains, which is critical in complex asynchronous or functional Swift codebases.
When NOT to use
Avoid using 'rethrows' when your function needs to throw errors from its own logic unrelated to passed functions. In such cases, use 'throws' instead. Also, if your function calls multiple throwing functions but needs to throw custom errors or handle errors internally, 'rethrows' is not suitable.
Production Patterns
In production Swift code, 'rethrows' is commonly used in standard library functions like 'map', 'filter', and 'forEach' that accept closures which may throw. This pattern allows these functions to propagate errors from closures without forcing all calls to handle errors unnecessarily. It is also used in custom higher-order functions to build flexible, reusable APIs that integrate error handling seamlessly.
Connections
Higher-order functions
Rethrowing functions build on the idea of higher-order functions by adding error propagation capabilities.
Understanding rethrowing clarifies how higher-order functions can safely handle errors from passed functions, enhancing functional programming in Swift.
Exception handling in other languages
Rethrowing in Swift is similar to rethrowing exceptions in languages like Java or C#, but with stricter compile-time checks.
Comparing Swift's rethrows with exception rethrowing in other languages highlights Swift's focus on safety and explicit error propagation.
Supply chain error propagation
Like how a supply chain passes delays or defects from one supplier to the next, rethrowing functions pass errors from called functions up the call chain.
Seeing error propagation as a supply chain helps understand how errors flow through layers without being created or fixed prematurely.
Common Pitfalls
#1Using 'rethrows' when the function throws errors internally.
Wrong approach:func faulty() rethrows { throw NSError(domain: "Error", code: 1, userInfo: nil) }
Correct approach:func correct() throws { throw NSError(domain: "Error", code: 1, userInfo: nil) }
Root cause:Misunderstanding that 'rethrows' only allows throwing errors from parameters, not from the function's own code.
#2Calling a rethrowing function with a throwing closure but forgetting 'try'.
Wrong approach:perform { throw NSError(domain: "Error", code: 1, userInfo: nil) } // Missing 'try' before perform
Correct approach:try perform { throw NSError(domain: "Error", code: 1, userInfo: nil) }
Root cause:Not realizing that when passing a throwing closure, the call to the rethrowing function must be marked with 'try'.
#3Using 'try' when calling a rethrowing function with a non-throwing closure.
Wrong approach:try perform { print("No error") }
Correct approach:perform { print("No error") }
Root cause:Believing 'try' is always required with rethrowing functions, even when the closure cannot throw.
Key Takeaways
Rethrowing functions in Swift only throw errors if the functions they call throw errors, enabling flexible error propagation.
The 'rethrows' keyword restricts throwing to errors from function parameters, preventing throwing from the function's own code.
When calling rethrowing functions, 'try' is required only if the passed closure can throw, reducing unnecessary error handling.
Understanding rethrowing helps write cleaner, safer higher-order functions that integrate error handling naturally.
The Swift compiler uses 'rethrows' to enforce error propagation rules and optimize code, improving performance and safety.

Practice

(1/5)
1. What does the rethrows keyword indicate in a Swift function?
easy
A. The function cannot throw any errors.
B. The function always throws an error regardless of the closure.
C. The function only throws an error if the closure it receives throws an error.
D. The function catches all errors internally and never propagates them.

Solution

  1. Step 1: Understand the role of rethrows

    The rethrows keyword means the function itself does not throw errors unless the closure it accepts throws.
  2. Step 2: Compare options with this meaning

    The function only throws an error if the closure it receives throws an error. correctly states this behavior. Options B, C, and D misunderstand the conditional throwing nature of rethrows.
  3. Final Answer:

    The function only throws an error if the closure it receives throws an error. -> Option C
  4. Quick Check:

    rethrows means conditional throwing = A [OK]
Hint: Remember: rethrows means throw only if closure throws [OK]
Common Mistakes:
  • Thinking rethrows means always throws
  • Confusing rethrows with throws
  • Assuming rethrows catches errors
2. Which of the following is the correct syntax to declare a function that rethrows errors from a closure parameter named operation?
easy
A. func perform(operation: () -> Void) rethrows {}
B. func perform(operation: () rethrows -> Void) throws {}
C. func perform(operation: () throws -> Void) throws {}
D. func perform(operation: () throws -> Void) rethrows {}

Solution

  1. Step 1: Identify correct placement of throws and rethrows

    The closure parameter must be declared with throws if it can throw, and the function must be marked rethrows to propagate errors conditionally.
  2. Step 2: Check each option

    func perform(operation: () throws -> Void) rethrows {} correctly declares the closure as () throws -> Void and the function as rethrows. func perform(operation: () rethrows -> Void) throws {} incorrectly uses rethrows on the closure. func perform(operation: () throws -> Void) throws {} marks the function as throws instead of rethrows. func perform(operation: () -> Void) rethrows {} declares a non-throwing closure but marks the function as rethrows, which is invalid.
  3. Final Answer:

    func perform(operation: () throws -> Void) rethrows {} -> Option D
  4. Quick Check:

    Closure throws + function rethrows = A [OK]
Hint: Closure uses throws, function uses rethrows keyword [OK]
Common Mistakes:
  • Using rethrows on closure instead of function
  • Marking function as throws instead of rethrows
  • Marking function rethrows with non-throwing closure
3. What will be the output of the following Swift code?
func execute(task: () throws -> String) rethrows -> String {
    return try task()
}

func successTask() throws -> String {
    return "Success"
}

func failTask() throws -> String {
    throw NSError(domain: "Error", code: 1)
}

print(try execute(task: successTask))
medium
A. Success
B. Error thrown but caught silently
C. Compilation error due to rethrows misuse
D. Runtime crash without error handling

Solution

  1. Step 1: Understand function behavior with successTask

    The execute function calls task() which is successTask here. Since successTask returns "Success" without throwing, execute returns "Success".
  2. Step 2: Analyze output of print statement

    The print statement outputs the returned string "Success". No errors are thrown or caught here.
  3. Final Answer:

    Success -> Option A
  4. Quick Check:

    Successful closure call prints result = B [OK]
Hint: If closure succeeds, rethrowing function returns its result [OK]
Common Mistakes:
  • Assuming error is thrown when closure succeeds
  • Confusing rethrows with always throws
  • Expecting silent error handling
4. Identify the error in this Swift code snippet:
func run(operation: () throws -> Void) rethrows {
    do {
        try operation()
    } catch {
        print("Caught error")
    }
}

func risky() throws {
    throw NSError(domain: "Fail", code: 0)
}

try run(operation: risky)
medium
A. The function cannot be marked rethrows if it catches errors internally.
B. The closure parameter must not be marked throws.
C. The catch block should rethrow the error to propagate it.
D. The call to run must be inside a do-catch block.

Solution

  1. Step 1: Understand rethrows and error catching

    A function marked rethrows must not catch errors from the closure; it only propagates them. Catching errors inside breaks this rule.
  2. Step 2: Analyze the code's catch block

    The run function catches errors from operation() internally, so it cannot be marked rethrows. This causes a compile error.
  3. Final Answer:

    The function cannot be marked rethrows if it catches errors internally. -> Option A
  4. Quick Check:

    rethrows disallows internal catch = D [OK]
Hint: rethrows functions must not catch errors inside [OK]
Common Mistakes:
  • Marking function rethrows but catching errors inside
  • Confusing rethrows with throws
  • Expecting rethrows to handle errors silently
5. You want to write a Swift function processAll that takes an array of closures, each possibly throwing an error, and calls them all. If any closure throws, processAll should rethrow that error. Which is the best way to declare processAll?
hard
A. func processAll(tasks: [() throws -> Void]) rethrows { for task in tasks { try task() } }
B. func processAll(tasks: [() throws -> Void]) throws { for task in tasks { try task() } }
C. func processAll(tasks: [() -> Void]) rethrows { for task in tasks { task() } }
D. func processAll(tasks: [() throws -> Void]) { for task in tasks { try? task() } }

Solution

  1. Step 1: Understand rethrows limitation

    The rethrows keyword only works if the function takes exactly one throwing closure parameter. Here, tasks is an array of closures, so rethrows cannot be used.
  2. Step 2: Choose correct error propagation

    Since multiple closures can throw, processAll must be marked throws to propagate errors. func processAll(tasks: [() throws -> Void]) throws { for task in tasks { try task() } } correctly uses throws. func processAll(tasks: [() throws -> Void]) rethrows { for task in tasks { try task() } } incorrectly uses rethrows. func processAll(tasks: [() -> Void]) rethrows { for task in tasks { task() } } uses non-throwing closures. func processAll(tasks: [() throws -> Void]) { for task in tasks { try? task() } } ignores errors with try?.
  3. Final Answer:

    func processAll(tasks: [() throws -> Void]) throws { for task in tasks { try task() } } -> Option B
  4. Quick Check:

    Multiple throwing closures require throws, not rethrows = C [OK]
Hint: Use throws when multiple throwing closures are parameters [OK]
Common Mistakes:
  • Using rethrows with multiple throwing closures
  • Ignoring errors with try?
  • Declaring non-throwing closures when errors can occur
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard