I have been scanning this forum for a while now without any luck to find a solution. I am heavy into Result
type. Comes with years of Rust.
I can't stand untyped try
, it's really comical if you look at any code using do-catch:
func myFunction() {
do {
try somethingRisky()
} catch SomeError {
// handle SomeError
} catch {
// should never happen but if it does, make sure to implode so that the code can compile
fatalError()
}
}
Because if you don't handle that Pokemon catch-them-all case, the compiler will sure add one for you if you have a function that throws
, little surprise and automagic that should not have been there. One of things that really bothers me.
I myself sometimes resort to wrapping a try
call into Result
and then asserting the Error type, it sure was designed for that:
let result = Result({ try somethingRisky() }).mapError({ error in
// I kid you not, it can only be that one error
return error as! MyError
})
Been waiting for some sort typed throws ErrorType
for years but not seeing it anywhere on the horizon, although I enjoy the try
syntax to certain extent, it has some weird quirks too, i.e:
let data: Data
do {
data = try JSONEncoder().encode(...)
} catch {
print("Got that pesky \(error)")
return
}
// Do something with data...
It would have been so much more ergonomic to have a normal guard-let construct that could pass errors to the else branch, i.e:
guard let data = try JSONEncoder().encode(...) else {
// The error is missing though!
print("Got that pesky \(error)")
return
}
// Do something with data...
It's the same with Result
, it's impossible to guard case it the same way, i.e:
guard case .success(let value) = callFunctionReturningResult() else {
// Since we can't obtain failure this syntax is basically cannot be used
}
Unless of course you do something like that:
let result = callFunctionReturningResult()
guard case .success(let value) = result else {
return result
}
Sure the one could switch-case
into infinity but it creates unreadable code where usually the success
branch is the one that has all the meat.
Result
handling overall could have been seriously improved by supporting early returns Rust-style, i.e:
func myFunction() -> Result<T, F> {
// trailing ? as an early return
let data: Data = callFunctionReturningResult().mapError { ... }?
return .success(1337)
}
Compiler could expand that to something like:
func myFunction() -> Result<T, F> {
let data: Data
let result = callFunctionReturningResult().mapError { ... }
switch result {
case .success(let value):
data = value
case .failure:
return result
}
return .success(1337)
}
But implications are such that ergonomics and number of lines needed to handle Result are reduced to one. Surprises me that after all that time Swift has not improved a single bit on error handling and reducing amount of noise in code. Maybe it's just me feeling all that frustration punching sheets of boilerplate code on my keyboard, that should have been auto-generated for me?