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.