The Result Type: Modeling Success and Failure

The Result type is a sealed box for an operation's outcome: it holds either a success value or a failure error. It's used in asynchronous code like network requests to create clean, explicit completion handlers. The footgun is forgetting to handle both cases.
WHY IT EXISTS: Before Result, Swift developers handled failable operations with inconsistent patterns. Returning nil on failure meant you lost the reason for the error. Using a tuple like (Value?, Error?) created ambiguous states where both or neither value could be present. Result was introduced to provide a single, standard, and type-safe way to model both success and failure.
THE MENTAL MODEL: Think of Result as a sealed package from a delivery service. The package contains either the item you ordered (the Success value) or a note explaining why it couldn't be delivered (the Failure error). You cannot have both, and you cannot have an empty box. You must explicitly open it, usually with a switch statement, to see which one you got.
HOW IT WORKS: Result is a generic enum with two cases: success(Success) and failure(Failure). The Success associated value is the type you expect on a successful run, like Data from a download. The Failure associated value must be a type that conforms to Swift's Error protocol, giving you a detailed reason for the failure. This structure forces you to acknowledge and handle both possibilities at the call site.
WHEN TO USE IT: Result shines in asynchronous programming, especially with completion handlers. A network call's completion handler can be simplified from (Data?, Error?) -> Void to (Result<Data, APIError>) -> Void. This makes the call site cleaner and safer, as the compiler can enforce that you handle both success and failure. It's perfect for any callback-based API where errors can't be propagated up the call stack with throw.
WHEN NOT TO USE IT: For synchronous functions, using throws is often more idiomatic Swift. It allows the caller to use a standard do-try-catch block, which can be more readable and integrates better with other throwing functions. Use Result when you need to capture and pass an error value as a concrete object, especially across asynchronous boundaries.
ONE CANONICAL EXAMPLE: A function to fetch a user profile: func fetchProfile(id: String, completion: @escaping (Result<Profile, NetworkError>) -> Void). If the network request succeeds, you call completion(.success(userProfile)). If the server returns a 404, you create a specific error and call completion(.failure(.notFound)). The caller then uses a switch on the result to update the UI with the profile or show an appropriate error message.
Read the original → developer.apple.com
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.