# Error Handling — Swift

Source: https://www.skillbyai.com/en/swift/fn-errors

> Throw, propagate and handle errors with do-catch, try?, Result and typed throws.

## Errors as part of the signature

Swift functions that can fail are marked **`throws`**, and every call to them is marked **`try`**, so failure points are visible in code. Errors are values conforming to the **`Error`** protocol, usually enums with associated values. Handle them with **`do`/`catch`**, matching specific cases (`catch PaymentError.declined(let reason)`), or propagate them by making the caller `throws` too. **`try?`** converts a failure into `nil`, convenient when you only care whether something worked, and **`try!`** crashes on failure, so reserve it for cases that can only fail through programming errors. **Swift 6** added **typed throws**: `func charge() throws(PaymentError)`, which lets callers know the exact error type and switch over it exhaustively, useful in libraries and embedded code (plain `throws` remains the common default). **`Result<Success, Failure>`** stores a success or failure as a value, handy for callback APIs and caching outcomes. **`defer`** guarantees cleanup however a scope exits. Swift errors are not exceptions: there is no expensive stack unwinding, and throwing is a normal return path.

## Throwing, catching and typed throws

Callers see every failure point through try.

```swift
import Foundation

enum PaymentError: Error {
    case invalidAmount
    case declined(reason: String)
    case networkUnavailable
}

func charge(_ amount: Double, cardToken: String) throws(PaymentError) -> String {   // typed throws (Swift 6)
    guard amount > 0 else { throw .invalidAmount }
    if cardToken.hasPrefix("blocked") { throw .declined(reason: "Card blocked by issuer") }
    return "pay_" + UUID().uuidString.prefix(8)
}

do {
    let paymentId = try charge(499, cardToken: "tok_abc")
    print("Paid: \(paymentId)")
} catch .declined(let reason) {
    print("Declined: \(reason)")
} catch {
    print("Payment failed: \(error)")           // error is PaymentError here
}

let maybeId = try? charge(-5, cardToken: "tok")   // nil on failure

func loadConfig(at path: String) throws -> String {
    let file = try openFile(path)
    defer { closeFile(file) }                       // runs however the scope exits
    return try readAll(file)
}
```

## try? hides the reason

`try?` discards the error, so a failure is indistinguishable from an empty result. Use it when the reason genuinely does not matter, and log or handle the error otherwise.

**Quiz:** What does try? do with a thrown error?

- [x] Converts the result to nil when an error is thrown
- [ ] Crashes the app
- [ ] Rethrows it automatically
- [ ] Prints it

*Answer:* Converts the result to nil when an error is thrown. try? turns failures into nil optionals, discarding the error value.
