Skip to content

Instantly share code, notes, and snippets.

@brennanMKE
Created May 30, 2025 21:44
Show Gist options
  • Save brennanMKE/6a8dfff43f39154e96f73086c6f258cd to your computer and use it in GitHub Desktop.
Save brennanMKE/6a8dfff43f39154e96f73086c6f258cd to your computer and use it in GitHub Desktop.
Swift Errors vs Exceptions

🧠 Distinction: Thrown Errors (Swift) vs Exceptions (Other Languages)

✅ Swift: Thrown Errors

  • Swift’s throw/try/catch is designed for predictable, recoverable errors.

  • Swift requires explicit syntax:

    • func foo() throws
    • try foo()
    • do { try foo() } catch { ... }
  • Errors must conform to Error protocol (typically enums).

  • Swift errors don't unwind the stack unpredictably like exceptions in some languages.

  • No finally block, encouraging use of defer instead.

❌ Java/C++/Python: Exceptions

  • Exceptions often include both recoverable and unrecoverable errors (e.g., null dereferences).
  • Exception types can be caught at runtime based on inheritance hierarchy.
  • Exception handling is often lazy or optional, leading to silent error swallowing.

🔥 Common Anti-Patterns (in Any Language)

  1. Catching and ignoring all errors:

    do {
      try someFunction()
    } catch {
      // silently ignored
    }

    ❌ Hides bugs and prevents recovery logic.

  2. Using exceptions for control flow:

    • Overusing throw for non-exceptional cases (e.g., to signal end-of-loop). ❌ Makes code harder to follow and debug.
  3. Catching too broadly:

    catch {
      // Catches everything, even things you shouldn't handle here.
    }
  4. Overuse of generic error enums with no actionable info:

    enum MyError: Error {
      case unknown
    }

✅ Encouraged Swift Patterns

  1. Use Error enums with associated values:

    enum NetworkError: Error {
      case badURL(String)
      case timeout(seconds: Int)
      case unauthorized
    }
  2. Use do/try/catch narrowly:

    • Catch specific cases and recover meaningfully.
    do {
      try fetchData()
    } catch NetworkError.timeout(let seconds) {
      retry(after: seconds)
    }
  3. Avoid error swallowing — always log or handle intentionally:

    catch {
      logger.error("Failed to fetch data: \(error)")
    }
  4. Leverage Result type for deferred error handling:

    func loadImage() -> Result<Image, ImageError>
  5. Use try? or try! only when safe or explicitly handled:

    • try? converts to optional: good for fallback logic.
    • try! crashes if an error is thrown: use only when you're absolutely certain.
  6. Use Swift Concurrency’s async throws idiomatically:

    func fetchUser() async throws -> User
    • Call sites use try await to clearly show suspension and error risk.

Summary

Concept Swift Other Languages (e.g., Java)
Terminology "Error", throw "Exception", throw
Checked/Unchecked All errors are explicit Often mixed or unchecked
Type system integration Uses Error conforming types Often built-in exception hierarchy
Preferred usage Predictable failure paths Both predictable and crash-like
Recovery mechanism do / try / catch try / catch / finally

Swift encourages clarity and safety, making error handling composable, explicit, and recoverable — not as an afterthought, but as a first-class part of control flow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment