Menu
Coddy logo textTech

Propagating Errors

Part of the Logic & Flow section of Coddy's Swift journey — lesson 47 of 56.

A throwing function can call other throwing functions. The error propagates up to the nearest do/catch automatically, no manual rethrowing needed.

enum AuthError: Error { case badUser, badPass }

func loadUser(_ name: String) throws -> String {
    if name.isEmpty { throw AuthError.badUser }
    return name
}

func login(_ name: String, _ pass: String) throws -> String {
    let u = try loadUser(name)            // forwards the error
    if pass.count < 4 { throw AuthError.badPass }
    return "hello \(u)"
}

do {
    print(try login("", "abcd"))
} catch AuthError.badUser {
    print("missing user")
} catch AuthError.badPass {
    print("weak password")
}

Read top to bottom: login calls loadUser with try. If loadUser throws, the error skips past login's body and lands at the outer catch. You write each step to fail fast and let the caller decide what to do.

challenge icon

Challenge

Medium

Build a tiny pipeline of two throwing steps.

Define StepError: Error with two cases: negative, tooBig.

Define parseSafe(_ s: String) throws -> Int: throws .negative when the parsed integer is below 0, otherwise returns it. (Assume input parses, you've handled that elsewhere.)

Define capStrict(_ n: Int) throws -> Int: throws .tooBig when n > 100, otherwise returns n.

Define processStep(_ s: String) throws -> Int that calls parseSafe then capStrict, letting any error propagate.

Read a single line of input: a comma-separated list of strings. For each one, call processStep in a do/catch and print one line:

  • ok: <n> on success
  • negative when parseSafe threw
  • too big when capStrict threw

For input 10,-1,200,42, the output is:

ok: 10
negative
too big
ok: 42

Cheat sheet

A throwing function can call other throwing functions with try. Errors propagate automatically up to the nearest do/catch:

enum AuthError: Error { case badUser, badPass }

func loadUser(_ name: String) throws -> String {
    if name.isEmpty { throw AuthError.badUser }
    return name
}

func login(_ name: String, _ pass: String) throws -> String {
    let u = try loadUser(name)   // error propagates up if thrown
    if pass.count < 4 { throw AuthError.badPass }
    return "hello \(u)"
}

do {
    print(try login("", "abcd"))
} catch AuthError.badUser {
    print("missing user")
} catch AuthError.badPass {
    print("weak password")
}

If loadUser throws, execution skips the rest of login and lands directly at the outer catch. No manual rethrowing is needed.

Try it yourself

let parts = readLine()!.components(separatedBy: ",")

// TODO: enum StepError; parseSafe; capStrict; processStep;
// for each part, do/try processStep, catch each branch, print
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Logic & Flow