Last night I posted my initial thoughts about Swift's approach to error handling. I've softened a little to the approach as I got to play with it a bunch this morning, including re-visting what a Result<T, E> looks like in Swift.
It's only after going through that excercise again that reminded me just how clunky dealing with enum values are, especially those that contain values, that I can appreciate why the Swift team went in the direction they did.
enum MyCustomError: ErrorType {
case Happy
case Dance
}
enum Result<T, E> {
case Ok(T)
case Error(E)
}
func base() -> Result<(), MyCustomError> {
return Result.Error(MyCustomError.Dance)
}
func handle() {
let result = base()
switch (result) {
case .Ok(_):
println("required...")
case let .Error(error):
switch (error) {
case MyCustomError.Happy:
print("Happy error")
case MyCustomError.Dance:
print("Dance error")
}
}
}
Or, with the Swift error handling approach.
enum MyCustomError: ErrorType {
case Happy
case Dance
}
func base() throws {
throw MyCustomError.Dance
}
func handle() {
do {
try base()
}
catch MyCustomError.Happy {
print("Happy error")
}
catch MyCustomError.Dance {
print("Dance error")
}
catch {
print("catch all, because no types")
}
}
Of course… those aren't our only options though. See, there was this lovely little keyword guard that was also introduced to us. So, by putting some better smarts into Result<T, E>, we can get something that looks like this:
enum MyCustomError: ErrorType {
case Happy
case Dance
}
enum Result<T, E> {
case Ok(T)
case Err(E)
// what if these were simply generated for all enums?
var ok: T? {
switch self {
case let .Ok(value): return value
case .Err(_): return nil
}
}
var err: E? {
switch self {
case .Ok(_): return nil
case let .Err(e): return e
}
}
}
func base() -> Result<(), MyCustomError> {
return Result.Err(MyCustomError.Dance)
}
func handle() {
let result = base()
guard let value = result.ok else {
switch result.err! {
case MyCustomError.Happy:
print("Happy error")
case MyCustomError.Dance:
print("Dance error")
}
return
}
print("value: \(value)")
}
Swift's throws approach is still more terse, but provides no type-safety on the error type. It might be possible to stream-line the approach above some more, but it feels better to me. I never liked exceptions used as code flow control and the do-try-catch approach seems to just beg for that.
Eh… I'll keep noodling.