How it works
The explanation, with examples you can run.
The basics
import Foundation enum ParseError: Error { case empty case notANumber(String)} func parsePrice(_ text: String) throws(ParseError) -> Int { // typed throws, Swift 6.0 let trimmed = text.trimmingCharacters(in: .whitespaces) guard !trimmed.isEmpty else { throw .empty } guard let cents = Int(trimmed) else { throw .notANumber(trimmed) } return cents} for input in ["250", " ", "abc"] { do { print(try parsePrice(input)) } catch { // error is ParseError, not any Error switch error { case .empty: print("empty input") case .notANumber(let t): print("not a number: \(t)") } }} let maybe = try? parsePrice("12") // Int? : 12print(maybe ?? 0)do { } catch { }handles errors; severalcatchclauses can match patterns (catch ParseError.empty). An unqualifiedcatchbindserror.try?turns a thrown error intonil.try!traps if anything is thrown, the error version of!.defer { }runs when the scope is left by any path, including a throw, which is where cleanup goes. Since Swift 6.4, adeferblock can alsoawaitasync calls.rethrowsmarks a function that throws only if a closure passed to it does, likemap.
Typed throws
Before Swift 6, a throwing function could throw any Error, so a catch always received any Error and needed a fallback. Typed throws (SE-0413, Swift 6.0) lets a function declare the exact type: throws(ParseError). Callers that catch get a ParseError and can switch over it exhaustively. Two spellings fall out of the design: plain throws means throws(any Error), and throws(Never) means the function doesn't throw at all, which lets generic code such as map pass a closure's error type through.
The Swift book's advice is to keep plain throws as the default. Typed throws fits:
- code that only passes on the errors of a closure it was given (generic algorithms),
- an internal module where the set of failures really is closed,
- Embedded Swift and other constrained code where boxing an
any Errorisn't wanted.
For a public API it is a commitment: adding a new failure kind later is a source-breaking change, and errors from lower layers have to be wrapped into your type.
Result, and when not to throw
Result<Success, Failure: Error> stores an outcome as a value, for when you need to keep an error around or hand it across a callback boundary. try result.get() turns it back into a throw. With async/await, most callback-shaped Result code becomes a plain async throws function.
Not everything is an error. A missing dictionary key is an optional; a broken invariant is a precondition failure that stops the program. Throw when the caller can reasonably do something about the failure: retry, show a message, choose another path.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.