“Try” – wrestling with NSErrorPointer

A great aspect of Swift is its interoperation with Objective-C frameworks, allowing us to use existing APIs and code. While that works very well technically, Swift does promote a different style and this leads to some situations where the new is slightly at odds with the existing.

One such area is calls to methods with an NSErrorPointer parameter (i.e. NSError ** parameters in Objective-C):

let fm = NSFileManager.defaultManager()
var error: NSError?
let files = fm.contentsOfDirectoryAtPath(".", error: &error)

That doesn’t look very swift-y. Even in Objective-C the NSError ** parameter is kind of ‘ugly’ in the sense that it’s rather uncommon to pass parameters by reference and have them modified inside methods or functions.

In order to improve on this I’ve started using the following try function:

if let error = try({ error in
    let fm = NSFileManager.defaultManager()
    let files = fm.contentsOfDirectoryAtPath(".", error: error)
}) {
    println("failed with error: \(error)")
}

The implementation of try is rather simple:

func try(block: NSErrorPointer -> Void) -> NSError? {
    var error: NSError?
    block(&error)
    return error
}

Note that we now pass in error instead of &error to contentsOfDirectoryAtPath(...) call. Wrapping it like this gives us the opportunity to hide the fact that we’re dealing with an NSErrorPointer behind the scenes.

This sort of resembles the typical try-catch pattern, at least conceptually:

try {
    // success path
} except(error) {
    // error path with an error variable
}

But while this looks similar to a try-catch, it’s undeniable that the if let error = ... puts the error handling frontmost. The construct is dealing with the error handling mainly and the ‘happy path’ is pushed to the background.

Another approach, therefore, especially if the method being ‘tried’ has a return value, is to wrap this value and a potential error in a Result<T>:

switch try({ error in
    NSFileManager.defaultManager().contentsOfDirectoryAtPath(".", error: error)
}) {
case .Success(let value):
    println(value.unbox)
case .Failure(let error):
    println(error)
}

Result<T> is an enum tying result and error states together. I've first come across this in the great book Functional Programming in Swift by the objc.io folks.

While it’s a bit more verbose and doesn’t resemble a try-catch, it’s now very clear what the results are. It’s unfortunate that we need to unbox the value but that’s unavoidable, for now at least.

The whole definition of ‘try‘ is available in this gist, including Result<T>.