[swift-evolution] [Accepted] SE-0112: Improved NSError Bridging

Kevin Ballard kevin at sb.org
Thu Aug 4 22:12:52 CDT 2016

With NSError, you *must* check the domain before trying to interpret the
code, or else your code is buggy and will behave incorrectly when
receiving an unexpected error. With SE-0112, instead of checking the
domain, you check if the Error can be casted to the particular error
type that represents the domain. There is a one-to-one correspondence
between domains and the new error types. For example, NSCocoaErrorDomain
is represented by CocoaError, NSURLErrorDomain is URLError, etc.

So previously you might have code that looks like

func handleError(error: NSError) {
    switch error.domain {
    case NSCocoaErrorDomain where error.code == NSFileNoSuchFileError:
        let path = error.userInfo[NSFilePathErrorKey] as? String
        // handle error for path
    case NSURLErrorDomain where error.code == NSURLErrorTimedOut:
        let url = error.userInfo[NSURLErrorKey] as? NSURL
        // handle error for url
        // generic handling of other errors

And now you'd write that like

func handleError(error: Error) {
    switch error {
    case let error as CocoaError where error.code ==
        let path = error.filePath
        // handle error for path
    case let error as URLError where error.code == .timedOut:
        let url = error.failingURL
        // handle error for url
        // generic handling of other errors

It's the same basic structure, except now you get strong typing, you
can't possibly forget to check the domain (which is a surprisingly
common bug I see in a lot of code), and you get convenient accessors for
the values stored in the user info.

And if you don't actually care about any of the user info properties,
then the new version is much simpler than the old:

func handleError(error: Error) {
    switch error {
    case CocoaError.fileNoSuchFileError:
        // handle error
    case URLError.timedOut:
        // handle error
        // generic handling of other errors

It's similar to checking the code without the domain in the old style,
except now it checks the domain automatically, so you *still* can't
accidentally interpret an error's code in the wrong domain.

-Kevin Ballard

On Thu, Aug 4, 2016, at 11:00 AM, Jon Shier via swift-evolution wrote:
> Doug:
> Thanks for indulging me so far, I think I’ve almost got it. Prior to
> this, using NSError, I could just look at the relevant properties of
> the error if I needed to see what type it was. Network errors had
> different codes from CloudKit errors, POSIX errors were underlying
> FileManager errors. A bit complex due to the undocumented nature of so
> many of these errors, but I could ignore any aspect of the error I
> didn’t care about. Now, however, it seems I must always care about
> what types of errors come out of various methods, as I’ll need to cast
> to the appropriate types to get useful information. For example, how
> would you handle the CloudKit errors I mentioned before? It seems to
> me like I would need to, at the point where I need to extract useful
> information, do a switch on various casts. First, try casting to
> CKError, then to CocoaError (?), and then likely produce a fatalError
> if there’s an unexpected type. Or is Error guaranteed to always cast
> to something useful? I’ve read the proposal a few times now and it
> looks like a lot of casting is going to be required, I’m mostly
> curious about the recommended patterns, especially for asynchronous
> calls that don’t go through throw/catch.
> Jon
>> On Aug 2, 2016, at 5:36 PM, Douglas Gregor <dgregor at apple.com> wrote:
>>> On Aug 2, 2016, at 2:19 PM, Jon Shier <jon at jonshier.com> wrote:
>>> Thanks Doug. I missed the rename, as earlier points still referred
>>> to ErrorProtocol. In regards to the CloudKit errors, I appreciate
>>> the strongly typed CKError, but why not have the methods return that
>>> type directly?
>> Generally speaking, Cocoa only uses NSError—not specific subclasses
>> or NSError or other error types—because errors can occur at many
>> different places in the stack and be propagated up. A CloudKit
>> operation could fail because of some problem detected in a different
>> error domain—say, the general Cocoa error domain or URLError
>> domain—and that non-CloudKit error would get passed through
>> immediately. So, if you were assuming that every error you get here
>> had to be in the CloudKit error domain, I believe your code was
>> already incorrect. It is *possible* that CloudKit translates/wraps
>> all other errors, but that would be odd for a Cocoa framework.
>>> Every usage of these methods is going to require such a cast, so why
>>> require it in the first place? I don’t understand what advantage
>>> erasing the strongly type error that was just created has when the
>>> developer will just have to bring it right back. Or is this just a
>>> first implementation?
>> There was never a strongly-typed error, and in most Cocoa cases there
>> shouldn’t be one because NSError covers all error domains, by design.
>> - Doug
>>> Jon
>>>> On Aug 2, 2016, at 4:20 PM, Douglas Gregor <dgregor at apple.com>
>>>> wrote:
>>>>> On Aug 2, 2016, at 10:30 AM, Jon Shier via swift-evolution <swift-
>>>>> evolution at swift.org> wrote:
>>>>> I’m not sure where to put such feedback, but the ErrorProtocol to
>>>>> Error rename that accompanied the implementation of this proposal
>>>>> is very, very painful. It completely eliminates the very useful
>>>>> ability to embed an associated Error type inside other types, as
>>>>> those types now conflict with the protocol. Also, was this rename
>>>>> accompanied by an evolution proposal? It seems like the change was
>>>>> just made when this proposal was implemented.
>>>> The rename was part of the proposal, in bullet #5 of the proposed
>>>> solution (which, amusing, pastes as bullet #1 below):
>>>>  1. Rename ErrorProtocol to Error: once we've completed the
>>>>     bridging story, Error becomes the primary way to work with
>>>>     error types in Swift, and the value type to which NSError is
>>>>     bridged:

>>>> func handleError(_ error: Error, userInteractionPermitted: Bool)
>>>>> Also, the adoption of this proposal by the Cocoa(Touch) frameworks
>>>>> as seen in Xcode 8 beta 4 has made asynchronous error handling
>>>>> quite a bit more arduous. For example, the CKDatabase method
>>>>> fetch(withRecordID recordID: CKRecordID, completionHandler:
>>>>> (CKRecord?, Error?) -> Void) returns an `Error` now, meaning I
>>>>> have to cast to the specific `CKError` type to get useful
>>>>> information out of it. Is this just an unfortunate first effort
>>>>> that will be fixed, or is this the expected form of these sorts of
>>>>> APIs after this proposal?
>>>> Prior to this proposal, you would have had to check the domain
>>>> against CKErrorDomain anyway to determine whether you’re looking at
>>>> a CloudKit error (vs. some other error that is passing through
>>>> CloudKit), so error bridging shouldn’t actually be adding any work
>>>> here—although it might be making explicit work that was already
>>>> done or should have been done. Once you have casted to CKError, you
>>>> now have typed accessors for information in the error:
>>>> extension CKError {
>>>>   /// Retrieve partial error results associated by item ID.
>>>>   public var partialErrorsByItemID: [NSObject : Error]? {
>>>>     return userInfo[CKPartialErrorsByItemIDKey] as? [NSObject :
>>>>     Error]
>>>>   }
>>>>   /// The original CKRecord object that you used as the basis for
>>>>   /// making your changes.
>>>>   public var ancestorRecord: CKRecord? {
>>>>     return userInfo[CKRecordChangedErrorAncestorRecordKey] as?
>>>>     CKRecord
>>>>   }
>>>>   /// The CKRecord object that was found on the server. Use this
>>>>   /// record as the basis for merging your changes.
>>>>   public var serverRecord: CKRecord? {
>>>>     return userInfo[CKRecordChangedErrorServerRecordKey] as?
>>>>     CKRecord
>>>>   }
>>>>   /// The CKRecord object that you tried to save. This record is
>>>>   based
>>>>   /// on the record in the CKRecordChangedErrorAncestorRecordKey
>>>>   key
>>>>   /// but contains the additional changes you made.
>>>>   public var clientRecord: CKRecord? {
>>>>     return userInfo[CKRecordChangedErrorClientRecordKey] as?
>>>>     CKRecord
>>>>   }
>>>>   /// The number of seconds after which you may retry a request.
>>>>   This
>>>>   /// key may be included in an error of type
>>>>   /// `CKErrorServiceUnavailable` or `CKErrorRequestRateLimited`.
>>>>   public var retryAfterSeconds: Double? {
>>>>     return userInfo[CKErrorRetryAfterKey] as? Double
>>>>   }
>>>> }
>>>> - Doug
>>>>> Jon Shier
>>>>>> On Jul 12, 2016, at 8:44 AM, Shawn Erickson via swift-evolution
>>>>>> <swift-evolution at swift.org> wrote:
>>>>>> Thanks for the effort on the proposal and discussion and thanks
>>>>>> to those working in the implementation.
>>>>>> -Shawn
>>>>>> On Tue, Jul 12, 2016 at 12:25 AM Charles Srstka via swift-
>>>>>> evolution <swift-evolution at swift.org> wrote:
>>>>>>> Wow, thanks! I’m delighted that Apple found this improvement to
>>>>>>> be worth inclusion in Swift 3. This will truly make the language
>>>>>>> much nicer to use with the Cocoa frameworks.
>>>>>>> Thanks!
>>>>>>> Charles
>>>>>>> > On Jul 11, 2016, at 11:19 PM, Chris Lattner via swift-
>>>>>>> > evolution <swift-evolution at swift.org> wrote:
>>>>>>> >
>>>>>>> > Proposal Link:
>>>>>>> > https://github.com/apple/swift-evolution/blob/master/proposals/0112-nserror-bridging.md
>>>>>>> >
>>>>>>> > The review of "SE-0112: Improved NSError Bridging" ran from
>>>>>>> > June 30 ... July 4, 2016. The proposal has been *accepted*:
>>>>>>> >
>>>>>>> > The community and core team agree that this proposal is a huge
>>>>>>> > step forward that enriches the experience working with and
>>>>>>> > extending the Cocoa NSError model in Swift.  The core team
>>>>>>> > requests one minor renaming of
>>>>>>> > "attemptRecovery(optionIndex:andThen:)" to
>>>>>>> > "attemptRecovery(optionIndex:resultHandler:)”.  It also
>>>>>>> > discussed renaming CustomNSError and RecoverableError, but
>>>>>>> > decided to stay with those names.
>>>>>>> >
>>>>>>> > Thank you to Doug Gregor and Charles Srstka for driving this
>>>>>>> > discussion forward, and for Doug Gregor taking the charge on
>>>>>>> > the implementation effort to make this happen for Swift 3!
>>>>>>> >
>>>>>>> > -Chris Lattner
>>>>>>> > Review Manager
>>>>>>> >
>>>>>>> > _______________________________________________
>>>>>>> > swift-evolution mailing list
>>>>>>> > swift-evolution at swift.org
>>>>>>> > https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>>>> _______________________________________________
>>>>>>> swift-evolution mailing list
>>>>>>> swift-evolution at swift.org
>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>>> _______________________________________________
>>>>>> swift-evolution mailing list
>>>>>> swift-evolution at swift.org
>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>> _______________________________________________
>>>>> swift-evolution mailing list
>>>>> swift-evolution at swift.org
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
> _________________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160804/27f153f8/attachment.html>

More information about the swift-evolution mailing list