Swift uses a do-try-catch model for error handling that integrates well with the type system. Functions that can fail are marked with the throws keyword, and callers must explicitly handle the possibility of an error using try inside a do block, followed by catch clauses that match specific error types. A general catch clause can catch any remaining errors. Custom error types are typically defined as enums conforming to the Error protocol, with cases that can optionally carry associated values for additional context, such as case serverError(code: Int).
Swift provides three variants of try for different situations. The standard try requires the call to be inside a do-catch block so errors are explicitly handled. The try? variant converts the result into an optional, returning nil if an error is thrown, which is convenient when the caller only cares whether the call succeeded. The try! variant force-tries the call and crashes at runtime if an error is thrown, so it should only be used when an error is impossible. The rethrows keyword is used in functions whose only throwing behavior comes from a closure parameter; the function will only throw if the passed closure throws, otherwise callers do not need to use try.
Control flow in Swift is enhanced by two important statements. The guard statement requires a condition to be true to continue execution, and its else branch must exit the current scope through return, throw, break, or a similar mechanism. When used to unwrap an optional, the unwrapped value is available either only within the if block (with if let) or in the rest of the enclosing scope (with guard let), making guard let the preferred pattern for early exits. The defer statement, by contrast, runs a block of code just before the current scope exits, regardless of how it exits, which is useful for cleanup such as closing files. Multiple defer blocks within the same scope execute in reverse order of declaration, like a stack.