Question 6
Error handling — throws and Result
Compare throws/try/catch with Result for modeling failures.
Answer outline
throws / try / catch is the natural fit for linear flows — failure interrupts control flow and the happy path stays readable.
It also composes cleanly with async/await, making it the default choice for most Swift error handling.
Result is more useful when failure needs to be treated as a value.
Use it when storing an outcome, passing it across boundaries, combining results manually, or deferring error handling to a later point.
Principles
- Use
throwsfor linear flows — it keeps the happy path front and center. - Use
Resultwhen error state needs to be stored or passed as data rather than thrown immediately. - Model error types to reflect what the caller needs to handle, not internal implementation details.
throwscomposes naturally withasync/await; prefer it overResultin async contexts.
Use throws when the caller should handle failure as part of the normal control flow.
func loadProfile() throws -> Profile {
let data = try Data(contentsOf: profileURL)
return try JSONDecoder().decode(Profile.self, from: data)
}
do {
let profile = try loadProfile()
show(profile)
} catch {
showError(error)
}
Use Result when you want to store, return, or pass around success/failure without throwing immediately.
func loadProfileResult() -> Result<Profile, Error> {
do {
let profile = try loadProfile()
return .success(profile)
} catch {
return .failure(error)
}
}
let result = loadProfileResult()
switch result {
case .success(let profile):
show(profile)
case .failure(let error):
showError(error)
}