Update, August 22nd, 2014: Please see Error Handling – Take Two for some updated insights. It may be the case that named tuples are the right approach to solve this problem.
: .info
Error handling is something that seems to be have been left to the coder to figure out for Swift. Yes, there is the overly clunky and terribly awkward NSError
usage:
func contentsForType(typeName: String,
error: NSErrorPointer) -> AnyObject!
{
// some code, oops! an error
if error {
error.memory = NSError(domain: domain,
code: code,
userInfo: [:])
}
return nil
}
That is terrible. Not only do we need to use the NSError
class and jump over into the ObjC runtime, for absolutely no good reason, we get to use a terrible syntax that is not how Swift even wants you to work. At the very least error could be an inout
reference and we can do something nicer…
Well, I think we can do better: we can borrow from other languages! Haskell has a concept of an Either
type that is essentially "either this value or that". That's pretty much want we want: "either our return value or an error". Great! Let's implement that and see how it feels in Swift.
Let's start out with some requirements:
- Need to support error information for function calls that have no return value
- Need to support error information for function calls that return a value
- Pure Swift implementation
- Names that clearly identify what we are working with
With those requirements, we know that we are going to need three different items:
Error
—  a type that represents the error information. This will simply be modelled after theNSError
type
2 Failable
 —  a type that represents either the error state or a success state with no return value
FailableOf<T>
—  a type that represents either the error state or a success state with a return value
The Error
class is the easiest and most straight-forward:
struct Error {
typealias ErrorInfoDictionary = Dictionary<String, Any>
let code: Int
let domain: String
let userInfo: ErrorInfoDictionary
init(code: Int, domain: String, userInfo: ErrorInfoDictionary?) {
self.code = code
self.domain = domain
if let info = userInfo {
self.userInfo = info
}
else {
self.userInfo = [String:Any]()
}
}
}
There really is not much to say about the class: it is basically the Swift version of the NSError
class from ObjC.
Now, to tackle the Failable
type:
enum Failable {
case Success
case Failure(Error)
init() {
self = .Success
}
init(_ error: Error) {
self = .Failure(error)
}
var failed: Bool {
switch self {
case .Failure(let error):
return true
default:
return false
}
}
var error: Error? {
switch self {
case .Failure(let error):
return error
default:
return nil
}
}
}
An enum is the perfect choice for us because a Failable
has exactly two states: Success
and Failure
. There are no other possibilites.
On top of that, we have some helper methods failed and error that prevent us from having to write this terrible code to get the values out each time:
switch result {
case .Failure(let error):
// handle the error here
default:
// no error, do something else
}
Instead, we can write much more natural code:
let result = failableMethod()
if result.failed {
// handle the error
}
// continue on
I'm going to pause here. Some of you may be wondering, why not implement the LogicValue
protocol? Great question! Because that protocol is the DEVIL!
if result {
// handle the error?? or does this mean there is a result???
}
// continue on?
I find the protocol extremely ambiguous and error prone. That's why I'm not using it.
The FailableOf<T>
is not that much different, other than a fairly annoying bug in Swift that cannot generate the correct code—more on that in a minute.
enum FailableOf<T> {
case Success(FailableValueWrapper<T>)
case Failure(Error)
init(_ value: T) {
self = .Success(FailableValueWrapper(value))
}
init(_ error: Error) {
self = .Failure(error)
}
var failed: Bool { /* same as above … */ }
var error: Error? { /* same as above … */ }
var value: T? {
switch self {
case .Success(let wrapper):
return wrapper.value
default:
return nil
}
}
}
There are a few changes:
- A value is stored for the Success state
- A new value property has been added
- There is this pesky
FailableValueWrapper
There is a bug in Swift where the compiler cannot generate the code for an enum that does not have a fixed layout, which would be the case if the Success
value could hold any old type of T
. To get around this, we can create a wrapper class so that the FailableOf<T>
can essentially use the class pointer size for laying about the enum. Hopefully this gets addressed in later drops.
That's basically it. Now we can handle errors in a much more Swift-friendly way. Below is some sample use case:
func failWhenNilAndReturn(name: String?) -> FailableOf<T> {
// not using .Success because of the
// FailableValueWrapper workaround.
if let n = name { return FailabeOf<T>(n) }
else { return .Failure(Error(code: 2,
domain: "err",
userInfo: nil)) }
}
let result = failWhenNilAndReturn("David")
if result.failed { /* handle the error */ }
println("name: \(result.value!)")
I'm not the first to write on this topic, nor will I be the last. This is definitely an odd area that was left out for Swift…
The full source code can be found in my SwiftLib project on GitHub: https://github.com/owensd/SwiftLib.