Charles, While I agree it's unfortunate that there isn't much interop between ErrorType and NSError, the decision from the core team to make ErrorType an empty protocol seems to have been a premeditated one. I would love to have someone on the core team respond here in my stead, but I'll try and present my side of it.
I have a lot of errors that come up internally in my app's workings. I subscribe to Swift's error handling rationale regarding recoverable errors. For example, my internal networking library has a NoInternetError which never needs to be presented using built-in cocoa error presentation, but instead has a different presentation scheme for each different context (e.g. if it happened while loading a tableview, it shows up as the placeholder text. if it happened in response to a user action, it displays an alert.) I, therefore, strongly disagree with adding any extra members to the ErrorType protocol: it's cruft that will never be of use to me. Now, this is the part that I'm not sure about and I might be corrected by someone from the core team: It seems that part of the rationale for introducing the general ErrorType is to move *away* from NSError. NSError is antiquated and carries information that is often not even used (take a survey of 100 swift programmers, and maybe 40 of them will care to tell you what an error domain is). A lot of this probably hinges on typed `throws` annotations as well; if we get those, then you never have to catch general ErrorType unless you wrote a function to throw general ErrorType, so you could make all your error types conform to a custom protocol with all this NSError-conformant info in it without worrying about static dispatch. On Sat, Dec 19, 2015 at 10:50 PM Charles Srstka via swift-evolution < [email protected]> wrote: > This is a bit of a pre-proposal; I wanted to bounce some ideas off the > community before writing up a formal proposal, to see how people felt about > this. > > The Problem: > > Swift introduces the very nifty ErrorType protocol, which, if implemented > as an enum, allows one to associate arguments with the specific error cases > with which they are relevant, as in this example: > > enum MyError: ErrorType { > case JustFouledUp > case CouldntDealWithFile(url: NSURL) > case CouldntDealWithSomeValue(value: Int) > } > > This is great, because it ensures that the file-related error will always > have a URL object, whereas it won’t be included in the cases where it is > irrelevant. > > Unfortunately, the Cocoa APIs needed to display an error object to the > user all take NSErrors, and the conversion from other ErrorTypes to > NSErrors is not very good; using “as NSError” to convert a MyError will > result in something that has all of the associated values removed, and the > message displayed to the user will be a cryptic and not particularly > user-friendly “MyError error 1” rather than something more descriptive. One > can define an “toNSError()” method on one’s own error type that will > properly propagate the NSError’s userInfo dictionary such that it will be > displayed in a more meaningful way: > > enum MyError: ErrorType { > case JustFouledUp > case CouldntDealWithFile(url: NSURL) > case CouldntDealWithSomeValue(value: Int) > > func toNSError() -> NSError { > var userInfo = [String : AnyObject]() > let code: Int > > > switch self { > case .JustFouledUp: > userInfo[NSLocalizedFailureReasonErrorKey] = "Something > fouled up!" > code = 0 > case let .CouldntDealWithFile(url): > userInfo[NSLocalizedFailureReasonErrorKey] = "Something went > wrong with the file \(url.lastPathComponent ?? "(null)")." > userInfo[NSURLErrorKey] = url > code = 1 > case let .CouldntDealWithSomeValue(value): > userInfo[NSLocalizedFailureReasonErrorKey] = "This value > isn't legit for some reason: \(value)" > code = 2 > } > > > return NSError(domain: "MyError", code: code, userInfo: userInfo) > } > } > > However, since this method will only be invoked if called explicitly, one > has to make sure to include .toNSError() every time when throwing an error > object, or else it will not display properly; one can never just throw a > MyError object. This is error-prone (no pun intended), and also prevents > things like making the error conform to Equatable and comparing it against > an ErrorType we received from some other method. > > I propose an addition to the ErrorType protocol, provisionally entitled > “toNSError()”, but which another name could be given if something else is > determined to be more appropriate. This method would be called by the > system whenever “as NSError” is called on something that implements > ErrorType. While this method would be added to the protocol, a default > implementation would be provided in an extension, so that implementers of > ErrorType would never actually need to override it unless very specific > customization was required. For this default implementation, several other > properties corresponding to some of the more commonly-used NSError keys > (defined with default implementations returning nil) would be defined and > referenced, and anything that returned non-nil would be packaged into a > userInfo dictionary, like so: > > protocol ErrorType { > var description: String? { get } > var failureReason: String? { get } > var recoverySuggestion: String? { get } > var recoveryOptions: [String]? { get } > var recoveryAttempter: AnyObject? { get } > var helpAnchor: String? { get } > var underlyingError: ErrorType? { get } > var URL: NSURL? { get } > > > func toNSError() -> NSError > } > > extension ErrorType { > var description: String? { return nil } > var failureReason: String? { return nil } > var recoverySuggestion: String? { return nil } > var recoveryOptions: [String]? { return nil } > var recoveryAttempter: AnyObject? { return nil } > var helpAnchor: String? { return nil } > var underlyingError: ErrorType? { return nil } > var URL: NSURL? { return nil } > > > func toNSError() -> NSError { > let domain = // do what “as NSError” currently does to generate > the domain > let code = // do what “as NSError” currently does to generate the > code > > > var userInfo = [String : AnyObject]() > > > if let description = self.description { > userInfo[NSLocalizedDescriptionKey] = description > } > > if let failureReason = self.failureReason { > userInfo[NSLocalizedFailureReasonErrorKey] = failureReason > } > > > if let suggestion = self.recoverySuggestion { > userInfo[NSLocalizedRecoverySuggestionErrorKey] = suggestion > } > > > if let options = self.recoveryOptions { > userInfo[NSLocalizedRecoveryOptionsErrorKey] = options > } > > > if let attempter = self.recoveryAttempter { > userInfo[NSRecoveryAttempterErrorKey] = attempter > } > > if let anchor = self.helpAnchor { > userInfo[NSHelpAnchorErrorKey] = anchor > } > > > if let underlying = self.underlyingError { > userInfo[NSUnderlyingErrorKey] = underlying.toNSError() > } > > > if let url = self.URL { > userInfo[NSURLErrorKey] = url > > > if url.fileURL, let path = url.path { > userInfo[NSFilePathErrorKey] = path > } > } > > > return NSError(domain: domain, code: code, userInfo: userInfo) > } > } > > Thanks to all the default implementations, the error type would only have > to implement the properties corresponding to the userInfo keys that the > implementer deems relevant, as in: > > enum MyError: ErrorType { > case JustFouledUp > case CouldntDealWithFile(url: NSURL) > case CouldntDealWithSomeValue(value: Int) > > > var failureReason: String? { > switch self { > case .JustFouledUp: > return "Something fouled up!" > case let .CouldntDealWithFile(url): > return "Something went wrong with the file \(url. > lastPathComponent ?? "(null)")." > case let .CouldntDealWithSomeValue(value): > return "This value isn't legit for some reason: \(value)" > } > } > > > var URL: NSURL? { > switch self { > case let .CouldntDealWithFile(url): > return url > default: > return nil > } > } > } > > This could then be created and passed to an API taking an NSError like so: > > let err = MyError.CouldntDealWithFile(url: NSURL(fileURLWithPath: > "/path/to/file")) > > NSApp.presentError(err as NSError) > > and everything would be properly presented to the user. > > Similar functionality could be added to the protocol for conversions in > the other direction, although this would be more difficult and would > require more work on the implementer’s part. > > The biggest problem I see to the idea is the use of references to > Foundation types—NSError and NSURL—in the ErrorType definition, which may > be undesired in a pure-Swift environment. In particular, usage of the NSURL > type for the ‘URL’ property, which could have useful applications outside > of simple Obj-C interop, could be irksome. Normally I would just propose > adding things in an extension, but of course in this case, declaring > methods in protocol extensions causes them to be statically dispatched, > which could result in the wrong methods being called if the caller thought > it was looking at a generic ErrorType rather than the specific concrete > type. Perhaps this could spark a new discussion on whether there ought to > be a way to declare dynamically-dispatched methods in protocol extensions. > It’s also possible that Swift could use a built-in URL type, equivalent to > Foundation’s NSURL, eliminating the need for any NS types other than > NSError here. It’s also possible that since there appears to be an > open-source implementation of Foundation in the github repository, that > this isn’t even an issue and is something we can just leave in. At any > rate, I thought this might be an interesting starting point for discussion. > > Of course, an alternative solution could be to define “domain”, “code”, > and “userInfo” properties (I know the first two are already in there, but > this would make them public) in the protocol and just use those. These > could also get default implementations that would work similarly to what is > above, which, if Swift gained a native URL type, could completely eliminate > Foundation types from the public interface. > > What do you think? > > Charles > > _______________________________________________ > swift-evolution mailing list > [email protected] > https://lists.swift.org/mailman/listinfo/swift-evolution >
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
