[swift-evolution] [Discussion] Adopting a new common error type outside the bounds of NSError
kevin at sb.org
Mon Mar 7 02:16:54 CST 2016
On Sun, Mar 6, 2016, at 11:10 PM, Brent Royal-Gordon wrote:
> >> `code` is still magically synthesized, as indicated by the imaginary `@_numbered` attribute. I don't like that very much, but I don't see a good alternative to it, either—RawRepresentable synthesis *almost* works, except that cases with associated values wouldn't be supported.
> > I disagree that this should be part of Error. `domain` and `code` are both relics of NSError integration and have no purpose when not bridging to NSError. The `domain` is really just a string representation of the error type, and the `code` is just an integral representation of the enum variant, so they're just imperfect representations of information already contained by the error value itself. We do actually need to include them in order to bridge any arbitrary Error to NSError, but they should stay as hidden implementation details (e.g. `_domain` and `_code`) like they are today. Besides, the names `domain` and `code` might be names that the concrete Error implementation actually wants to use for its own purposes.
> To tell the truth, the early drafts of that email said that Error's requirements should be platform-dependent, but that the Swift compiler should always be able to synthesize all of them automatically. But as I thought about it more, I decided that probably wasn't right.
> The reason I think that is Corelibs Foundation. It appears to me that the long-term plan for Swift is to have Foundation available in all environments Swift supports, except perhaps embedded ones. That means NSError is here to stay and interoperation between NSError and Swift.Error is going to be a permanent requirement.
The existence of Corelibs Foundation doesn't mean that Foundation will be something everyone is expected to use in the future. After all, it needs to be API-compatible with the real Foundation, but Foundation's API was designed for Obj-C, not Swift. A freshly-designed Foundation-free library that provides the same functionality would look rather different. Which is to say, the existence of Corelibs Foundation does not mean that the long-term plan is to keep using NSError.
> > Also, there's no need to ever "unpack" userInfo. When you bridge a Swift error into NSError, it uses a private NSError subclass _SwiftNativeNSError, which boxes up the original error, and when that NSError is bridged back to ErrorType you get the original error value back automatically. Swift has an internal type _ObjectiveCBridgeableErrorType that has an init?(_bridgedNSError: NSError) method, but AIUI that is actually used to handle bridging of regular Cocoa NSErrors into the Swift ErrorType values that represent them (e.g. for all of the error enums found in the overlay module).
> Yes, and I'm suggesting we should handle bidirectional bridging with public APIs, not private magic, and in a way that can expose all of the details to both sides. If NSError is a permanent part of Swift, I think we should plan to do that sooner or later.
The point is that, for error types defined in Swift, you'll _never_ have to manually unpack an NSError into the Swift error, because every NSError that represents the Swift error will have been created from the Swift error in the first place and will therefore be a _SwiftNativeNSError. The only reason to have a public API here is if you want to add the ability to construct new NSError instances in Obj-C and then convert them into your Swift error type, but I don't think that's something the stdlib needs to support.
> >> CustomStringConvertible for Simple Error Messages
> >> ----------------------------------------------------------------------
> >> I think that we should encourage developers to conform errors with user-readable error messages to CustomStringConvertible and implement `description` to return that error. To that end, when a CustomStringConvertible Swift error is bridged to NSError, its `description` should become the NSError's `localizedDescription`. That ends up looking like this:
> > I disagree here too. My CustomStringConvertible representation of my error may not be at all suitable for the localizedDescription of an NSError. The NSError's localizedDescription should be a string that is suitable for presenting to the user, but CustomStringConvertible isn't necessarily intended for showing to a user. For example, my enum's string representation may be "IOError(code: 3, path: "/tmp/foo")", which is certainly not what you want the user to see.
> Primarily, I am suggesting that we should have *a standard protocol* for "give me a textual version of this instance that I can show to a user" and that we should, by convention, use that on `Error`s to convey error messages for errors that are suitable to present to users.
You'd want to invent a new protocol for this purpose, then, as there's no built-in protocol that is defined such that it produces values suitable for NSError.localizedDescription. CustomStringConvertible just provides some way to produce a String from a value. It doesn't say anything about what that String is meant for. And the types of strings that you want for NSError.localizedDescription are rather unlikely to be the strings that people choose to return from CustomStringConvertible.description. localizedDescription is typically a complete grammatical sentence, whereas CustomStringConvertible usually just provides a description of the value itself
> I am not sure if `CustomStringConvertible` is the right protocol for that purpose, but if it isn't—if `CustomStringConvertible` is meant to provide a programmer-readable representation—then frankly I'm not sure what `CustomDebugStringConvertible` is for. But the exact protocol used is immaterial.
CustomDebugStringConvertible is meant for printing even more verbose descriptions in the debugger or in debug logging. Many types choose to provide a reasonable amount of info in CustomStringConvertible and a much more verbose output for CustomDebugStringConvertible.
> (To be honest, a lot of the decisions around `CustomStringConvertible` confuse me; for example, I don't understand why Swift allows you to interpolate any instance into a string rather than only `CustomStringConvertible` instances. So this might just be part of the same blind spot.)
Why shouldn't Swift allow you to interpolate other values? There's nothing about string interpolation that says the output is intended to be displayed in a UI, and interpolating arbitrary values is extremely useful for logging purposes.
> > And FWIW, I don't think I've _ever_ seen anyone provide localizedRecoverySuggestion with NSError, and it's pretty rare to see a localizedFailureReason.
> I think this may be why we're disagreeing about this so much—I suspect you use a very shallow subset of NSError's features, whereas I use them very aggressively. So let me explain where I'm coming from here.
> In my current project, a mixed-language Mac app with about 30 errors, I have an NSError user info provider* that's about 300 lines long. Every error has a localizedDescription, most have a localizedRecoverySuggestion, and a perhaps a third to a half have a localizedFailureReason (mainly ones which end up getting wrapped in a generic "Document X cannot be opened" error; I append the localizedFailureReason to indicate what happened.) To support this, I of course have to pack a bunch of information into the `userInfo`; I have 14 keys I use for various purposes, and most of their values get inserted into error messages to make them more specific.
> Structuring errors that thoroughly takes a lot of really boring code, but it pays off in really good error messages and diagnostics. I can usually pass an error straight into `-presentError:` with little to no modification.
> (I don't know how you *wouldn't* use a `localizedRecoverySuggestion`, honestly; those provide the small text in an error dialog.)
I think this is the difference between iOS and OS X. iOS has no equivalent to -presentError:, and nobody wants to cram a lot of text into an alert. So in iOS, nobody ever bothers with -localizedRecoverySuggestion and whatnot, and -localizedFailureReason use is pretty rare.
More information about the swift-evolution