Pattern matching on error codes

Foundation overloads the pattern matching operator ~= to enable matching against error codes in catch clauses.

catch clauses in Swift support pattern matching, using the same patterns you’d use in a case clause inside a switch or in an if case … statement. For example, to handle a file-not-found error you might write:

import Foundation

do {
    let fileURL = URL(filePath: "/abc") // non-existent file
    let data = try Data(contentsOf: fileURL)
} catch let error as CocoaError where error.code == .fileReadNoSuchFile {
    print("File doesn't exist")
} catch {
    print("Other error: \(error)")

This binds a value of type CocoaError to the variable error and then uses a where clause to check the specific error code.

However, if you don’t need access to the complete error instance, there’s a shorter way to write this, matching directly against the error code:

      let data = try Data(contentsOf: fileURL)
- } catch let error as CocoaError where error.code == .fileReadNoSuchFile {
+ } catch CocoaError.fileReadNoSuchFile {
      print("File doesn't exist")

Foundation overloads ~=

I was wondering why this shorter syntax works. Is there some special compiler magic for pattern matching against error codes of NSError instances? Turns out: no, the answer is much simpler. Foundation includes an overload for the pattern matching operator ~= that matches error values against error codes.1

The implementation looks something like this:

public func ~= (code: CocoaError.Code, error: any Error) -> Bool {
    guard let error = error as? CocoaError else { return false }
    return error.code == code

The actual code in Foundation is a little more complex because it goes through a hidden protocol named _ErrorCodeProtocol, but that’s not important. You can check out the code in the Foundation repository: Darwin version, swift-corelibs-foundation version.

This matching on error codes is available for CocoaError, URLError, POSIXError, and MachError (and possibly more types in other Apple frameworks, I haven’t checked).

  1. I wrote about the ~= operator before, way back in 2015(!): Pattern matching in Swift and More pattern matching examples↩︎