Lesson 84Error Handling

Try? and Try!

Simplified Error Handling

Sometimes you don't need full do-try-catch. Swift provides try? and try! for simpler scenarios.

try? vs try!

try? (Optional Try)

Returns optional - nil if error

let x = try? riskyFunc()

Safe, loses error info

try! (Force Try)

Crashes if error thrown

let x = try! riskyFunc()

Dangerous, use sparingly

When to Use try?

  • When you don't care about the specific error
  • When you have a default/fallback value
  • When checking if something succeeds (bool-like)
  • Combined with if let, guard let, ??

When to Use try!

Only when failure is impossible:

  • Loading bundled resources that must exist
  • In unit tests where you want to fail fast
  • Programmer errors (assertions)

Never use try! with user input or network data!

Comparison Summary

SyntaxOn SuccessOn Error
tryReturns valueJumps to catch
try?Returns optional valueReturns nil
try!Returns valueCRASH!
main.swift
// TRY? AND TRY!
// Alternative ways to handle throwing functions

// SETUP
enum FileError: Error {
    case notFound
    case corrupted
    case permissionDenied
}

func readFile(name: String) throws -> String {
    if name.isEmpty {
        throw FileError.notFound
    }
    if name == "corrupted.txt" {
        throw FileError.corrupted
    }
    return "Contents of \(name)"
}

// ===== TRY? (Optional Try) =====
// Converts result to optional - nil if error thrown

let content1 = try? readFile(name: "data.txt")
// content1 is String? = "Contents of data.txt"

let content2 = try? readFile(name: "")
// content2 is String? = nil (error was thrown)

// Using with optional binding
if let content = try? readFile(name: "config.txt") {
    print("Got content: \(content)")
} else {
    print("Failed to read file")
}

// Using with nil coalescing
let data = try? readFile(name: "missing.txt") ?? "Default content"
print(data)  // "Default content"

// Using with guard
func processFile(name: String) {
    guard let content = try? readFile(name: name) else {
        print("Cannot process - file not readable")
        return
    }
    print("Processing: \(content)")
}

// Chaining multiple try?
let result = try? readFile(name: "a.txt")
    .uppercased()
    .trimmingCharacters(in: .whitespaces)

// ===== TRY! (Force Try) =====
// Crashes if error is thrown - use only when certain no error will occur

// SAFE: We know this won't throw
let knownGoodContent = try! readFile(name: "safe.txt")

// DANGEROUS: This will crash!
// let crash = try! readFile(name: "")  // Fatal error!

// When to use try!
// 1. When failure is truly impossible
// 2. In tests where you want to fail fast
// 3. When loading bundled resources that must exist

// Example: Loading bundled JSON
func loadBundledConfig() -> [String: Any] {
    let url = Bundle.main.url(forResource: "config", withExtension: "json")!
    let data = try! Data(contentsOf: url)
    return try! JSONSerialization.jsonObject(with: data) as! [String: Any]
}

// ===== COMPARISON =====

// try - Full error handling
do {
    let a = try readFile(name: "file.txt")
    print(a)
} catch {
    print("Error: \(error)")
}

// try? - Optional result, ignore specific error
let b: String? = try? readFile(name: "file.txt")

// try! - Force unwrap, crash on error
let c: String = try! readFile(name: "known-good.txt")

// ===== PRACTICAL PATTERNS =====

// Pattern 1: Default value with try?
func getConfig(key: String) throws -> String {
    throw FileError.notFound
}

let config = (try? getConfig(key: "theme")) ?? "default"

// Pattern 2: Multiple attempts with try?
func fetchFromPrimary() throws -> Data { throw FileError.notFound }
func fetchFromBackup() throws -> Data { return Data() }

let data2 = try? fetchFromPrimary() ?? (try? fetchFromBackup())

// Pattern 3: Converting try? to Bool
func validate(input: String) throws {
    if input.isEmpty { throw FileError.notFound }
}

let isValid = (try? validate(input: "test")) != nil

// Pattern 4: Using try? in conditions
if (try? readFile(name: "lock.txt")) != nil {
    print("File is locked")
}

// Pattern 5: Async try?
func fetchUser() async throws -> String {
    return "User"
}

// In async context:
// let user = try? await fetchUser()

Try It Yourself!

Create a JSON parsing function and use try? with nil coalescing to provide a default object when parsing fails!