Mastering Result Types in Swift: Handling Errors with Elegance

Rahul Goel
2 min readSep 5, 2023

A Comprehensive Guide to Error Handling and Result Enumerations

Result Types in Swift

In Swift, the Result type was introduced in Swift 5.0 as a native way to handle success and error cases for operations that can fail. It's defined as an enum with two associated values: .success(T) for successful results and .failure(Error) for errors. Here's a simplified implementation of the Result type in Swift:

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

In this implementation:

  • Success represents the type of the successful result.
  • Failure represents the type of the error that can occur.

Usage:

Here’s how you can use the Result type to represent the outcome of an operation:

Function that can return a Result

// A function that can return a Result
func divide(_ a: Int, by b: Int) -> Result<Int, Error> {
if b == 0 {
return .failure(NSError(domain: "DivisionError", code: 1, userInfo: nil))
} else {
return .success(a / b)
}
}

// Usage of the divide function
let result1 = divide(10, by: 2)
switch result1 {
case .success(let value):
print("Result: \(value)") // Output: "Result: 5"
case .failure(let error):
print("Error: \(error)")
}

let result2 = divide(10, by: 0)
switch result2 {
case .success(let value):
print("Result: \(value)")
case .failure(let error):
print("Error: \(error)") // Output: "Error: Error Domain=DivisionError Code=1"
}

In this example, the divide function returns a Result with an integer value on success and an error on failure. You can use a switch statement to handle both success and failure cases.

Network Request with Result:

import Foundation

enum NetworkError: Error {
case noData
case requestFailed
}

func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
// Simulate a network request
DispatchQueue.global().async {
if let data = someNetworkRequest() {
completion(.success(data))
} else {
completion(.failure(NetworkError.requestFailed))
}
}
}

// Calling fetchData
fetchData { result in
switch result {
case .success(let data):
// Handle successful data
print("Received data: \(data)")
case .failure(let error):
// Handle the error
print("Error: \(error)")
}
}

Parsing JSON with Result:

import Foundation

struct User: Codable {
let id: Int
let name: String
}

enum ParsingError: Error {
case invalidData
}

func parseJSON(data: Data) -> Result<User, Error> {
do {
let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: data)
return .success(user)
} catch {
return .failure(ParsingError.invalidData)
}
}

// Calling parseJSON
let jsonData = """
{
"id": 1,
"name": "Rahul Goel"
}
""".data(using: .utf8)!

let result = parseJSON(data: jsonData)
switch result {
case .success(let user):
print("Parsed user: \(user)")
case .failure(let error):
print("JSON parsing error: \(error)")
}

Follow me Rahul Goel for regular updates.

--

--

Rahul Goel

Computer Science Enthusiast | 11+ Year of Software Evolution | @Sharechat, Groupon, Paytm, Myntra https://www.linkedin.com/in/therahulgoel/