Swift 2 introduces a new error handling mechanism that integrates seamlessly with existing Cocoa frameworks. In the introductory talk, it was explicitly stated that Swift 2 does not use exceptions to propagate errors but rather a (much faster) ‘return value’ mechanims (my words, I don’t quite remember what it was called).
Curious as to what this might look like under the hood and remembering that the SIL can be inspected quite easily, I poked around a bit. Starting from
func doStuff() throws -> String {
...
}
we can dump the SIL as follows:
swiftc -emit-sil test.swift > test.sil
This is a simple text file and within it, we find what our method signature is compiled into:
%11 = function_ref @_TF8Contents7doStuffFzT_SS : $@convention(thin) () -> (@owned String, @error ErrorType) // user: %12
Don’t be put off by the mangled name _TF8Contents7doStuffFzT_SS
for doStuff
. It’s an encoding of module name, method name, parameters and return types into a single string. What we also see is that apparently the error handling turns our method into:
func doStuff() -> (String, ErrorType)
So it looks like it is using something quite similar to the Result<T> approach that’s become quite common for error handling.
At the call site of doStuff
we see the following:
try_apply %11() : $@convention(thin) () -> (@owned String, @error ErrorType), normal bb1, error bb4 // id: %12
bb1(%13 : $String): // Preds: bb0
...
bb3: // Preds: bb4
debug_value %44 : $ErrorType // let error // id: %30
...
bb4(%44 : $ErrorType): // Preds: bb0
br bb3 // id: %45
which to my (untrained) eye looks like a switch routing to bb1
for the normal case and through bb4
and then to bb3
for the error. Note the implicit let error
making the variable available to the catch
clause.
Repeating the exercise with an NSError
based Cocoa method shows something slightly different. Looking at the SIL for
do {
let fm = NSFileManager.defaultManager()
let files = try fm.contentsOfDirectoryAtPath("/foo")
print(files)
} catch {
print("An error occurred.")
}
we see
%17 = class_method [volatile] %14 : $NSFileManager, #NSFileManager.contentsOfDirectoryAtPath!1.foreign : NSFileManager -> (String) throws -> [String] , $@convention(objc_method) (NSString, AutoreleasingUnsafeMutablePointer<Optional<NSError>>, NSFileManager) -> @autoreleased Optional<NSArray> // user: %36
To me this looks like it is mapping to the ‘old’ signature of
fm.contentsOfDirectoryAtPath("/foo", &error)
behind the scenes automatically. Further down this is picked up in
%36 = apply %17(%25, %33, %14) : $@convention(objc_method) (NSString, AutoreleasingUnsafeMutablePointer<Optional<NSError>>, NSFileManager) -> @autoreleased Optional<NSArray> // users: %37, %44
...
switch_enum %36 : $Optional<NSArray>, case #Optional.Some!enumelt.1: bb1, case #Optional.None!enumelt: bb4 // id: %44
So in this case it doesn’t look like the error is returned via a tuple as in our first example but rather just switches on the NSError
coming out of the Cocoa framework call as we know it.
From this point on both mechanisms appear to essentially do the same thing, with the slight twist that the NSError
is converted to ErrorType
:
// function_ref swift_convertNSErrorToErrorType
%79 = function_ref @swift_convertNSErrorToErrorType : $@convention(thin) (@owned Optional<NSError>) -> @owned ErrorType // user: %80
I’m not sure yet how much I like Swift’s new error handling but it is certainly nicer than NSErrorPointer
and interesting to see how it’s being handled behind the scenes. I would guess it is much easier to tweak the mechanism given that so much of it is being handled at the SIL and standard library layer.