Errors: throw, try and catch

Swift · Practical · 12 min

Every file, network and decoding API in Swift throws, and you must handle errors explicitly to call it.

Errors are values conforming to the Error protocol — typically an enum: enum ParseError: Error { case empty, badNumber(String) }.

A function that can fail is marked throws. It uses throw to fail, and every call to it must be marked with try, so failure points are visible in the source.

Handle errors with do/catch:

do {
    let n = try parse(text)
} catch ParseError.badNumber(let s) {
    print("bad:", s)
} catch {
    print(error)          // implicit `error` binding
}

Alternatives to do/catch:

  • try? converts the result to an optional, discarding the error.
  • try! crashes on error — only for failures that are programmer mistakes.
  • Propagate by marking your own function throws and just writing try.

Swift 6 adds typed throws: func parse(_ s: String) throws(ParseError) -> Int. Callers then know the exact error type, and catch binds error as ParseError. Plain throws means throws(any Error). Typed throws suit low-level code and closed error sets; most app code keeps untyped throws.

Result<Success, Failure> stores an outcome as a value, which helps when passing results around; try result.get() turns it back into a throw.

Use defer { } to run cleanup when the scope exits, whether normally or by throwing.

Example

enum ParseError: Error {
    case empty
    case badNumber(String)
}

func parse(_ text: String) throws(ParseError) -> Int {
    guard !text.isEmpty else { throw .empty }
    guard let n = Int(text) else { throw .badNumber(text) }
    return n
}

for input in ["42", "", "x1"] {
    do {
        defer { print("checked \"\(input)\"") }
        print(try parse(input) * 2)
    } catch ParseError.badNumber(let s) {
        print("not a number:", s)
    } catch {
        print("other error:", error)
    }
}
// 84 / checked "42" / checked "" / other error: empty / ...
print(String(describing: try? parse("7")))   // Optional(7)

Remember

  • Mark failing functions throws and every call try
  • try? turns errors into nil; try! crashes
  • Typed throws throws(E) gives callers a concrete error type

Watch out for

  • Using try? everywhere and silently losing the reason something failed.

Check yourself

  1. What does `try? parse("abc")` evaluate to if parse throws?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.swift.org/swift-book/documentation/the-swift-programming-language/errorhandling/