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

Douglas Gregor dgregor at apple.com
Tue Jul 5 23:03:49 CDT 2016


> On Jul 5, 2016, at 5:54 PM, Ben Rimmington <me at benrimmington.com> wrote:
> 
>> 
>> On 6 Jul 2016, at 01:02, Douglas Gregor <dgregor at apple.com <mailto:dgregor at apple.com>> wrote:
>> 
>>> On Jul 5, 2016, at 5:00 PM, Ben Rimmington <me at benrimmington.com <mailto:me at benrimmington.com>> wrote:
>>> 
>>> <https://github.com/apple/swift-evolution/blob/master/proposals/0112-nserror-bridging.md <https://github.com/apple/swift-evolution/blob/master/proposals/0112-nserror-bridging.md>>
>>> 
>>> The new protocols could be combined into a single CustomNSError protocol.
>>> This would mirror the NSError class APIs, which are being customized.
>> 
>> Why is that good? The two primary protocols—LocalizedError and RecoverableError—provide a more focused, easy-to-understand experience for opting in to specific behavior. CustomNSError is a fallback for “I want to do something special with the generated NSError”.
> 
> You wrote previously:
> "To a close approximation, there are no use sites of these protocols."
> <http://thread.gmane.org/gmane.comp.lang.swift.evolution/22485/focus=22919 <http://thread.gmane.org/gmane.comp.lang.swift.evolution/22485/focus=22919>>
> 
> If the Printable protocol was renamed to CustomStringConvertible to discourage use sites, then having a single CustomNSError protocol should have a similar effect.

We don’t need to discourage use sites; I just don’t expect them to be common because it’s the library that will be querying these conformances. Regardless, we shouldn’t contort a protocol design to discourage usage; we should make it clear what conforming to the protocol implies.

> The protocol will be as easy-to-understand as the NSError class itself. If there are default implementations for all requirements, a conforming type can still opt into specific behavior.

“As easy-to-understand as the NSError class” is not the goal here. We want to do better, and that means detangling the various different things that NSError puts together.

With this proposal, you define an error type like this:

enum HomeworkError : ErrorProtocol {
  case forgotten
  case lost
  case dogAteIt
}

To give it a nice, localized error message (or other localized information), you conform to LocalizedError:

extension HomeworkError : LocalizedError {
  var errorDescription: String? {
    switch self {
    case .forgotten: return NSLocalizedString("I forgot it")
    case .lost: return NSLocalizedString("I lost it")
    case .dogAteIt: return NSLocalizedString("The dog ate it")
    }
  }
}

and if you want to implement recovery attempts for your error, you conform to RecoverableError:

extension HomeworkError : RecoverableError {
  // implementation here
}

You can catch these errors with “catch let error as HomeworkError”, or “catch HomeworkError.forgotten”, or whatever.

Nowhere in any of that did I mention NSError, or error domains, or codes, or user-info dictionaries. You shouldn’t need them with this model, at all. NSError is bridged away completely, and is an implementation detail of Objective-C interoperability. CustomNSError, and references to the NSError type, is an escape hatch so that one can get precise control over the interoperability with NSError for those (hopefully rare) cases where the Swift error-handling model can’t express something that NSError can.


> 
>>> Instead of using NSError.setUserInfoValueProvider(forDomain:provider:) 
>>> could you wrap the CustomNSError value inside an NSError subclass?
>>> 
>>> 	class _CustomNSError: NSError {
>>> 
>>> 	    private let _error: CustomNSError
>>> 
>>> 	    init(_error: CustomNSError) {
>>> 	        self._error = _error
>>> 	        super.init(
>>> 	            domain:   _error.dynamicType.errorDomain,
>>> 	            code:     _error.errorCode,
>>> 	            userInfo: _error.errorUserInfo)
>>> 	    }
>>> 
>>> 	    override var localizedDescription: String {
>>> 	        return _error.errorDescription ?? super.localizedDescription
>>> 	    }
>>> 
>>> 	    override var localizedFailureReason: String? {
>>> 	        return _error.failureReason ?? super.localizedFailureReason
>>> 	    }
>>> 
>>> 	    override var localizedRecoverySuggestion: String? {
>>> 	        return _error.recoverySuggestion ?? super.localizedRecoverySuggestion
>>> 	    }
>>> 
>>> 	    override var localizedRecoveryOptions: [String]? {
>>> 	        return _error.recoveryOptions ?? super.localizedRecoveryOptions
>>> 	    }
>>> 
>>> 	    override var recoveryAttempter: AnyObject? {
>>> 	        if _error.recoveryOptions != nil {
>>> 	            return _NSErrorRecoveryAttempter(error: _error)
>>> 	        } else {
>>> 	            return super.recoveryAttempter
>>> 	        }
>>> 	    }
>>> 
>>> 	    override var helpAnchor: String? {
>>> 	        return _error.helpAnchor ?? super.helpAnchor
>>> 	    }
>>> 	}
>> 
>> We could, but why? This is precisely what user-info value providers were designed for.
> 
> If the NSError.setUserInfoValueProvider(forDomain:provider:) method isn't available in all supported target platforms, an NSError subclass seems to be the simpler option.

When/where it is available, NSError.setUserInfoValueProvider(forDomain:provider:) is the solution recommended by Cocoa as the best way to lazily populate the userInfo dictionary, so it makes sense for us to use that mechanism. Sure, we need to implement a fallback, but the use of that fallback will go away at some point and we’ll be back to one implementation.

	- Doug


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160705/ce2194ff/attachment.html>


More information about the swift-evolution mailing list