Chapters

Hide chapters

Swift Cookbook

Live Edition · Multiplatform · Swift · Editor agnostic

Use a Result Type for Handling Errors in Swift
Written by Team Kodeco

The Result type is a generic enumeration that can indicate either a success or a failure. It’s defined in the Swift standard library and it’s a powerful tool for handling errors in a functional way.

The Result type has two cases, .success and .failure. It can be used to represent the outcome of a function that can throw an error.

enum Result<Success, Failure: Error> {
  case success(Success)
  case failure(Failure)
}

Here’s an example of how you can use the Result type to handle errors in a function that reads a file from disk and returns its content as a string:

import Foundation

func readFile(at path: String) -> Result<String, Error> {
  do {
    let content = try String(contentsOfFile: path)
    return .success(content)
  } catch {
    return .failure(error)
  }
}

Then you can call the function and handle the result in a switch statement, like this:

let result = readFile(at: "/path/to/file.txt")

switch result {
case .success(let content):
  print("Content: \(content)")
case .failure(let error):
  print("Error: \(error)")
}

When using the Result type, it’s important to remember that the success and failure cases are associated with generic types and they must be specified at the time of use. Also, it’s important to note that the failure case of the Result type must conform to the Error protocol.

Using Result Types with Custom Error Types

Another example of using the Result type to handle errors is by creating your own custom error type. This can be useful when you want to provide more context or specific information about the error that occurred.

Here’s an example of creating a custom error type called FileReadError that includes information about the path of the file that could not be read:

enum FileReadError: Error {
  case fileNotFound(path: String)
  case invalidFileFormat(path: String)
}

Then you can use this custom error type in readFile(:):

import Foundation

func readFile(at path: String) -> Result<String, FileReadError> {
  do {
    let content = try String(contentsOfFile: path)
    return .success(content)
  } catch {
    if let fileError = error as? FileReadError {
        return .failure(fileError)
    } else {
        return .failure(FileReadError.fileNotFound(path: path))
    }
  }
}

Then you can call the function and handle the result in a switch statement:

let result = readFile(at: "/path/to/file.txt")

switch result {
case .success(let content):
  print("Content: \(content)")
case .failure(let error):
  switch error {
  case .fileNotFound(let path):
    print("File not found at path: \(path)")
  case .invalidFileFormat(let path):
    print("Invalid file format at path: \(path)")
  }
}

In this example, the FileReadError is used to provide more context about the error that occurred. It also allows for more granular handling of the different types of errors that may occur, such as differentiating between a file not being found and an invalid file format.

© 2024 Kodeco Inc.