Swift Error Handling
Swift models recoverable failures with the Error protocol and the throw, try, and catch keywords, forcing you to explicitly acknowledge when a call might fail.
Defining Errors
Any type conforming to the empty Error protocol can be thrown, but enums are the most common choice because they let you group related failure cases together and switch over them exhaustively in a catch block.
Defining a Custom Error
enum ValidationError: Error {
case tooShort
case tooLong
case containsInvalidCharacters
}
func validate(username: String) throws -> String {
if username.count < 3 {
throw ValidationError.tooShort
}
if username.count > 20 {
throw ValidationError.tooLong
}
guard username.allSatisfy({ $0.isLetter || $0.isNumber }) else {
throw ValidationError.containsInvalidCharacters
}
return username
}Calling Throwing Functions
Calling a throwing function requires the try keyword, and try itself must appear inside a do-catch block or another throwing function. A do-catch block can have multiple catch clauses that match specific error cases, plus a catch-all clause at the end.
Handling Errors With do-catch
let candidates = ["ab", "validUser42", "bad*name"]
for candidate in candidates {
do {
let approved = try validate(username: candidate)
print("\(approved) is valid")
} catch ValidationError.tooShort {
print("\(candidate) is too short")
} catch ValidationError.tooLong {
print("\(candidate) is too long")
} catch {
print("\(candidate) failed: \(error)")
}
}try?, try!, and Cleanup With defer
Not every call site needs full error inspection. try? converts a thrown error into an optional nil, while try! asserts the call cannot fail and crashes at runtime if it does. defer schedules cleanup code that runs when the current scope exits, whether it exits normally or by throwing.
Optional and Forced Try, Plus defer
// try? converts a thrown error into nil
let maybeValid = try? validate(username: "ok")
print(maybeValid ?? "invalid") // invalid (too short)
// try! asserts the call cannot fail — crashes if it does
let forced = try! validate(username: "safeUser1")
print(forced) // safeUser1
func processFile() throws {
print("Opening file")
defer { print("Closing file") } // always runs, even on throw
throw ValidationError.tooLong
}
try? processFile()
// Prints: Opening file
// Closing file- try — propagate the error to the caller; requires a throwing function or a do-catch
- try? — turn any thrown error into nil, useful when you only care about success or failure
- try! — assert the call will always succeed; crashes the app if it throws
- do-catch — the only form that lets you inspect and react to the specific error thrown
Exercise: Swift Error Handling
What must a type do to be usable as a Swift error?