Use Cases

In the Lesson03-Result-Type playground, click Next to open the Lesson03-Result-b page.

Convert Throwing Expression to Result

Result has a nifty initializer init(catching:) to convert any throwing expression to a Result. You can use this to collect results while looping over a collection.

Returning to the bakery example’s orderPastry(item:amountRequested:flavor:), use init(catching:) to loop over itemsForSale, calling orderPastry to create an array of Result<Int, Error>:

let bakery = Bakery()
var results = [Result<Int, Error>]()  // 1

for (key, value) in bakery.itemsForSale {
  let result = Result {  // 2 
    try bakery.orderPastry(
      item: key,
      amountRequested: 1,
      flavor: value.flavor)
  }
  results.append(result)  // 3
}
  1. Create an empty array of Result<Int, Error>. When using this initializer, the .failure value is any Error, so you can’t declare an array of Result<Int, BakeryError>.
  2. While looping over the itemsForSale dictionary, call orderPastry(item:amountRequested:flavor:) with arguments that produce failure only if amountRequested is greater than numberOnHand. Instead of a do block, pass the throwing expression to the Result initializer init(catching:) and store the result.
  3. Append result to the array of Result<Int, Error>.

To test this code, modify itemsForSale to ensure failure for at least one item:

let itemsForSale = [
  "Cookie": Pastry(flavor: "ChocolateChip", numberOnHand: 20),
  "PopTart": Pastry(flavor: "WildBerry", numberOnHand: 0),  // guarantee error
  "Donut" : Pastry(flavor: "Sprinkles", numberOnHand: 24),
  "HandPie": Pastry(flavor: "Cherry", numberOnHand: 6)
]

Run the code and show results:

Results array
Results array

You get .success for three of the items, but PopTart results in .failure with associated value .tooFew and numberOnHand 0.

Completion Handlers

Click Next to open the Lesson03-Result-c page.

In the days before Swift concurrency, asynchronous APIs like URLSession used completion handlers to execute code when the asynchronous operation completed. The function would supply data, response, error values to the completion handler, but you couldn’t throw errors from the completion handler.

The Result type provides a solution for asynchronous functions with completion handlers. If you’re still using these instead of Swift concurrency’s async/await, this example from Saurabh Patel’s How to Implement Result Type in Swift: Step-by-Step Tutorial shows how you can use Result to capture error information.

First, here’s an Error enum and a typealias for the Result type:

enum DataFetchError: Error {
  case networkFailure
  case dataCorrupted
  case unauthorized
}

// Define a Result type for fetching data
typealias FetchResult = Result<Data, DataFetchError>

fetchData(from:completion:) fetches data, and its last parameter is a custom completion handler that is called within the completion handler of the asynchronous URLSession method dataTask(with:completion:):

func fetchData(
  from urlString: String,
  completion: @escaping @Sendable (FetchResult) -> Void) {  // 1
  guard let url = URL(string: urlString) else {
    completion(.failure(.networkFailure))  // 2
    return
  }

  // Simulate fetching data
  URLSession.shared.dataTask(with: url) { data, response, error in  // 3
    guard let data = data else {
      completion(.failure(.dataCorrupted))  // 4
      return
    }
    completion(.success(data))  // 5
  }.resume()
}
  1. The function’s completion parameter is a closure that takes a Result<Data, DataFetchError> argument and returns nothing.
  2. If urlString doesn’t create a valid URL, you pass this error to completion.
  3. The completion handler of dataTask(with:completion:).
  4. If data is nil, you pass this error to completion.
  5. If there’s data, you pass this value to completion.

Now, your custom completion handler receives data when fetchData(from:completion:) is successful and useful error information when the function call fails.

fetchData(from: "https://example.com") { result in
  switch result {
  case .success(let data):
    print("Successfully fetched \(data.count) bytes.")
  case .failure(let error):
    switch error {
    case .networkFailure:
      print("Network failure - Please check your connection.")
    case .dataCorrupted:
      print("Data corrupted - Unable to process the data received.")
    case .unauthorized:
      print("Unauthorized - Access denied.")
    }
  }
}

The completion argument of fetchData(from:completion:) is written as a trailing closure, which receives result from fetchData(from:completion:). It can switch over the Result and .failure cases, processing data in the .success case, and making appropriate suggestions in the .failure cases.

See forum comments
Download course materials from Github
Previous: Result Type Next: Conclusion