[Proposal idea] Improved interop for ErrorType->NSError


(Charles Srstka) #1

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


(Brent Royal-Gordon) #2

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.

I too badly want to be able to control the `userInfo` of an NSError, or at least have casting to NSError and back round-trip nicely.

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:

This, however, seems like a recipe for boilerplate.

What I would like to see is this:

* `var userInfo: [String: AnyObject] { get }` is added to `ErrorType`, or perhaps to a sub-protocol specifically for this purpose. There's a default implementation which simply returns `[:]`.

* A type like this exists in the standard library, or perhaps the Foundation overlay:

  private class _NSSwiftError: NSError {
    var swiftError: ErrorType
    
    init(swiftError: ErrorType) {
      self.swiftError = swiftError
      super.init(domain: swiftError.domain, code: swiftError.code, userInfo: swiftError.userInfo)
    }
  }

I don't know if it can actually be private, but it should be invisible to developers, and considered an implementation detail. It might also make sense to retrieve the `userInfo` from `swiftError` on demand—the exact implementation isn't very important here.

* Casting a native Swift error enum to `NSError` actually constructs an `_NSSwiftError`. (Bridged error code enums like `NSCocoaError` use `NSError` as usual.)

* Casting an `_NSSwiftError` back to `ErrorType` or a native Swift `ErrorType` enum pulls the original instance back out of `swiftError`, so all associated objects are preserved. (Bridged error code enums just construct a new instance.)

This way, the only code you have to write is a `userInfo` implementation, and even that only if you want to do something custom. There's no need to write a bunch of code to construct an entire `NSError` instance, or to unpack the `userInfo` back into a Swift enum.

···

--
Brent Royal-Gordon
Architechies


(Dennis Lysenko) #3

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 < swift-evolution@swift.org> 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
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Charles Srstka) #4

Whatever the solution is, it shouldn’t be invisible to developers. We need a good way to get non-NSError ErrorTypes to turn into NSErrors that have decently propagated userInfo dictionaries, so that they can be presented to the user in a form that isn’t just a cryptic “<domain> error <code>”.

Charles

···

On Dec 20, 2015, at 12:35 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

I don't know if it can actually be private, but it should be invisible to developers, and considered an implementation detail. It might also make sense to retrieve the `userInfo` from `swiftError` on demand—the exact implementation isn't very important here.


(Dennis Lysenko) #5

Sorry, got a little excited in answering and just re-read the proposal.
Ignore the part of "extra cruft", since I'll never have to see it unless I
want to override it. I thought that the static dispatch bit later on in the
proposal implies that that wouldn't work so well, but I might have misread.

···

On Sun, Dec 20, 2015 at 12:00 AM Dennis Lysenko <dennis.s.lysenko@gmail.com> wrote:

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 < > swift-evolution@swift.org> 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
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Colin Cornaby) #6

So here’s my 2 cents. I talked to Charles about this off list, so I’ll just say my piece and then move along. :slight_smile:

It seems to me that ErrorType is very empty because it’s meant to be a placeholder in the language for whatever the native error type is on the platform. When I’m in AppKit/UIKit/OSS Swift Foundation, I’m using NSError. When I’m using C API, my ErrorType would probably be a typedef’d int. When I’m using Win32, my ErrorType is whatever Win32 error types are. If I’m using Swift with some platform/API where the error types are some sort of struct, or even a string type, those types can be made to conform to error type. It’s meant to be a shallow wrapper because it’s just a placeholder to wrap the native platform API.

I’m not sure I like going down the path of Swift actually trying to replace platform API, especially if it requires a hand bridge. Then you’re constantly extending the language to bridge the error types from all the different platforms. My view is that OSS Swift Foundation provides a template where the platform API cannot be assumed, and that OSS Swift Foundation defines it’s own concrete error type in NSError/Error. Certainly NSError isn’t perfect and the conversation on how to make it better is worthwhile (especially now that NSError is part of Swift Foundation, and changes could also be adopted by Obj-C Foundation where relevant.)

A year ago my opinion on this was very different, and I was bothering people on the Swift team for a similar improvement based on where Swift was at the time. But with OSS Foundation and multiplatform Swift my feeling has changed. I’m not comfortable with trying to promote NSError concepts higher into the language when I think Swift conforming to the host platform’s error API is the better path. My assumption is that Swift is not meant to be a platform in itself (like Java), but just a language over an existing platform.

···

On Dec 19, 2015, at 9:00 PM, Dennis Lysenko via swift-evolution <swift-evolution@swift.org> wrote:

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 <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 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
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Chris Lattner) #7

I agree that our interop with NSError is unsatisfactory and should be a lot better. We had some discussions about improvements that could be made in this space late in the Swift 2 design cycle, but I’ve since paged them out. John McCall would be the best person to talk to about this, he drove much of the Swift 2 error handling design.

-Chris

···

On Dec 19, 2015, at 9:00 PM, Dennis Lysenko via swift-evolution <swift-evolution@swift.org> wrote:

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).


(Charles Srstka) #8

If the proposed changes are built right into the protocol, the dispatch is dynamic. If they were added via an extension, then we’re stuck with static dispatch unless some changes are made to the language (which is also possible).

Charles

···

On Dec 19, 2015, at 11:03 PM, Dennis Lysenko <dennis.s.lysenko@gmail.com> wrote:

Sorry, got a little excited in answering and just re-read the proposal. Ignore the part of "extra cruft", since I'll never have to see it unless I want to override it. I thought that the static dispatch bit later on in the proposal implies that that wouldn't work so well, but I might have misread.


(Brent Royal-Gordon) #9

Whatever the solution is, it shouldn’t be invisible to developers. We need a good way to get non-NSError ErrorTypes to turn into NSErrors that have decently propagated userInfo dictionaries, so that they can be presented to the user in a form that isn’t just a cryptic “<domain> error <code>”.

No, my point is that the developer should not have to do anything special to get this bridging, or explicitly create an _NSSwiftError—it should happen automatically. Swift should take care of preserving your associated values through casts to NSError and back, and fetching your userInfo as needed; all you should need to do is tell it how to express your error's userInfo.

Actually, now that I try it, it looks like Swift *does* do this much...

  1> import Foundation
  2> enum MyError: ErrorType { case FileNotFound (url: NSURL) }
  3> let url = NSURL(string: "missing.txt")!
url: NSURL = "missing.txt"
  4> MyError.FileNotFound(url: url)
$R0: MyError = FileNotFound {
  FileNotFound = {
    url = "missing.txt"
  }
}
  5> MyError.FileNotFound(url: url) as! NSError
$R1: NSError = domain: "MyError" - code: 0 {
  ObjectiveC.NSObject = {
    NSObject = {
      isa = _SwiftNativeNSError
    }
    _reserved =
    _code = 0
    _domain = "MyError"
    _userInfo = nil
  }
}
  6> (MyError.FileNotFound(url: url) as! NSError) as! ErrorType
$R2: MyError = FileNotFound {
  FileNotFound = {
    url = "missing.txt"
  }
}
  7> (MyError.FileNotFound(url: url) as! NSError) as! ErrorType as! MyError
$R3: MyError = FileNotFound {
  FileNotFound = {
    url = "missing.txt"
  }
}

Funny, I'm sure that didn't work last time I tried it...

Anyway, my point remains: this _SwiftNativeNSError should use a userInfo property on your ErrorType to populate NSError.userInfo. There should be no need to go through the full rigamarole of calling NSError's initializer yourself—just return a dictionary at the appropriate moment.

···

--
Brent Royal-Gordon
Architechies


(Charles Srstka) #10

Having a userInfo property by which I could return the dictionary would work fine, and I’d be perfectly happy with it. I’m not sure if the Swift team would be happy with such a solution, though, since it necessarily involves dynamically typed objects, whereas my proposal enforces the correct types for all of the individual objects within the userInfo dictionary; String for the failure reason, NSURL for the URL, [String] for the recovery options, etc.

Charles

···

On Dec 20, 2015, at 3:14 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

Anyway, my point remains: this _SwiftNativeNSError should use a userInfo property on your ErrorType to populate NSError.userInfo. There should be no need to go through the full rigamarole of calling NSError's initializer yourself—just return a dictionary at the appropriate moment.


(Charles Srstka) #11

And my two cents is:

NSError is antiquated, and enums conforming to ErrorType, with associated types on the case values, are much better. They are cleaner, they are easier to use, and they are nicer to look at.

Creating them is nicer:

let error = MyError.FileNotFound(url: someURL)

vs.

var userInfo: [String : AnyObject] = [NSLocalizedFailureReason : “The file \(someURL.lastPathComponent ?? “(null)”) could not be found.”, NSURLErrorKey : someURL]
if someURL.fileURL, let path = someURL.path { userInfo[NSFilePathErrorKey] = path }
let error = NSError(domain: “SomeArbitraryString”, code: Int(ErrorCodeEnum.FileNotFound.rawValue), userInfo: userInfo)

Comparing them is nicer:

if case let .FileNotFound(url) = error {
    // handle the error
}

vs.

if error.domain == “SomeArbitraryString” && error.code == Int(ErrorCodeEnum.FileNotFound.rawValue) {
  let url: NSURL

  if let theURL = error.userInfo[NSURLErrorKey] as? NSURL {
    url = theURL
  } else if let path = error.userInfo[NSFilePathErrorKey] as? String {
    url = NSURL(fileURLWithPath: path)
  } else {
    // handle not having a url, and bail out
  }

  // now handle the error
}

I don’t think any further explanation is necessary. :stuck_out_tongue:

The only real problem with ErrorType is sending it to legacy AppKit APIs that expect an NSError, since there’s no way to customize what happens when the user converts one of them via “as NSError”. You have to write your own conversion method, and then be scrupulous about always calling that instead of “as NSError”, which is particularly problematic if you receive the error as a plain ErrorType without knowing that it’s your particular error type, in which case you have to do runtime type checking to see if you should use the custom conversion method or the built-in “as NSError” conversion.

If this could be fixed, it’d be fantastic.

Charles

···

On Dec 21, 2015, at 5:36 PM, Colin Cornaby <colin.cornaby@mac.com> wrote:

So here’s my 2 cents. I talked to Charles about this off list, so I’ll just say my piece and then move along. :slight_smile:

It seems to me that ErrorType is very empty because it’s meant to be a placeholder in the language for whatever the native error type is on the platform. When I’m in AppKit/UIKit/OSS Swift Foundation, I’m using NSError. When I’m using C API, my ErrorType would probably be a typedef’d int. When I’m using Win32, my ErrorType is whatever Win32 error types are. If I’m using Swift with some platform/API where the error types are some sort of struct, or even a string type, those types can be made to conform to error type. It’s meant to be a shallow wrapper because it’s just a placeholder to wrap the native platform API.

I’m not sure I like going down the path of Swift actually trying to replace platform API, especially if it requires a hand bridge. Then you’re constantly extending the language to bridge the error types from all the different platforms. My view is that OSS Swift Foundation provides a template where the platform API cannot be assumed, and that OSS Swift Foundation defines it’s own concrete error type in NSError/Error. Certainly NSError isn’t perfect and the conversation on how to make it better is worthwhile (especially now that NSError is part of Swift Foundation, and changes could also be adopted by Obj-C Foundation where relevant.)

A year ago my opinion on this was very different, and I was bothering people on the Swift team for a similar improvement based on where Swift was at the time. But with OSS Foundation and multiplatform Swift my feeling has changed. I’m not comfortable with trying to promote NSError concepts higher into the language when I think Swift conforming to the host platform’s error API is the better path. My assumption is that Swift is not meant to be a platform in itself (like Java), but just a language over an existing platform.


(Paul Cantrell) #12

And my two cents is:

NSError is antiquated, and enums conforming to ErrorType, with associated types on the case values, are much better. They are cleaner, they are easier to use, and they are nicer to look at.

Digressing a bit, I’ve moved away from enums and toward structs for custom ErrorTypes, for several reasons:

1. Associated values are tuples. This is a big problem in a public API. Any tuple change is a breaking change, and errors thus cannot gain any new diagnostic information with breaking the API:

    enum MyError: ErrorType
        {
        case FileNotFound(url: String) // 1.0
        }

    ↓

    enum MyError: ErrorType
        {
        case FileNotFound(url: String, errno: Int?) // Oops! New major release
        }

Structs allow for future expansion:

    struct FileNotFound: ErrorType
        {
        let url: String // 1.0
        }

    ↓

    struct FileNotFound: ErrorType
        {
        let url: String
        let errno: Int? // No problem! 1.1
        }

2. Structs allow error-specific helper methods and derived properties, which can be useful.

3. Structs can implement protocols, which allow shared handling of error cases with shared structure.

Comparing them is nicer:

if case let .FileNotFound(url) = error {
    // handle the error
}

4. But comparing structs is nicer still, especially when dealing with an optional error. With an enum:

    func handleFileNotFound(error: ErrorType?)
        {
        if let error = error
            {
            if case MyError.FileNotFound(let url) = error
                { print(url) }
            }
        }

But if it’s a struct:

    func handleFileNotFound(error: ErrorType?)
        {
        if let error = error as? MyError.FileNotFound
            { print(error.url) }
        }

Or if you don’t need the wrapped value:

    func handleFileNotFound(error: ErrorType?)
        {
        if error is MyError.FileNotFound
            { retry() }
        }

So much more readable than the “if let, if case let” dance!

Hmm, I wonder if #1 or #4 suggest any language changes to propose for this list.

The one big advantage of the error enum is one can use it to enforce handling of all error types in a switch — a need which I have yet to encounter in practice.

Cheers,

Paul

···

On Dec 21, 2015, at 9:12 PM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:


(Colin Cornaby) #13

And my two cents is:

NSError is antiquated, and enums conforming to ErrorType, with associated types on the case values, are much better. They are cleaner, they are easier to use, and they are nicer to look at.

And I still think that’s a platform choice. If a platform wants to define it’s errors as a Swift enum, or if a developer is defining their own independent platform, that’s a semi-reasonable thing to do. But I think trying to force all the platforms (Apple and non-Apple) into an enum shaped box for cross platform Swift is going to be problematic. If the authors of Cocoa want to begin defining all their errors as enums, the ball is certainly in their court, but it doesn’t feel right to me to promote that up to a language concern.

Before multiplatform Swift, I was totally onboard with the idea of supercharging ErrorType, but the more I think about it, and the more I look at lower level stuff that we could begin using Swift for on other platforms, the more I think having larger error types needs to be a decision made by the host platform API on a case by case basis. And I’m not convinced an enum is an appropriate error tool for all cases, even though it is certainly appropriate for a lot of cases.

I do think there are some weird sharp edges around NSError/ErrorType that aren’t specific to NSError, and will continue cutting people’s fingers on other platforms (for example, if I had a C API that defined it’s own error type as a struct, I’d be having a lot of the same problems.) I haven’t been able to come up with any reasonable ideas though that would fix everything.

One thing I think could be done is that NSError could somehow be made to conform to RawRepresentable, which would then take care of a lot of this.

The code could then become:
enum MyError: NSError {
    case JustFouledUp(domain: .., code: .., userInfo: ..)
    case CouldntDealWithFile(domain: .., code: .., userInfo: ..)
    case CouldntDealWithSomeValue(domain: .., code: .., userInfo: ..)
}

…with no need for the functions to implement to provide out the other usual data. I know some consider the NSError initializers kind of awful, that’s something that could be dealt with at the NSError API level. I actually think the above looks a lot cleaner, although I know I skipped over some of the mess with ellipsis. And API developers don’t have to implement manual, hand rolled bridging.

This seems cleanest to me, and would carry going forward with Error in OSS Foundation.


(David Owens II) #14

Tangential to the topic at hand, but default values in tuples is something I’ve wanted for a while. Comparing optionals in a case statement would also be nice.

-David

···

On Dec 22, 2015, at 9:30 AM, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

Hmm, I wonder if #1 or #4 suggest any language changes to propose for this list.


(Chris Lattner) #15

If you dig through history, you’ll find that we had them at one point (perhaps even in the swift 1.x timeframe), and we even had varargs tuples. We removed both of them because they were infrequently used and adding a tremendous amount of complexity to the compiler (and were very buggy as a result).

-Chris

···

On Dec 22, 2015, at 9:44 AM, David Owens II via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 22, 2015, at 9:30 AM, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

Hmm, I wonder if #1 or #4 suggest any language changes to propose for this list.

Tangential to the topic at hand, but default values in tuples is something I’ve wanted for a while. Comparing optionals in a case statement would also be nice.


(Paul Cantrell) #16

If you squint a little, tuples are just anonymous structs with limited features. If you squint.

One of my favorite features of Swift is how (at least at the semantic level) it has no primitive types. Int is a struct! Brilliant! I’ve wondered on and off (even before this list) if perhaps the language could eliminate another fundamental type by making tuples just be sugar for structs.

What would that look like? I am thinking out loud here…

• Every tuple is secretly just an instance of an anonymous struct, much as optionals are secretly just enums.
• Under the covers, functions all take a single struct argument and return a single struct.
• Pattern matching can destructure structs. (Hmm, trouble here. Property ordering in structs is suddenly significant in the API, not just the ABI.)
• The tuple syntax infers the type of the struct to be created, and can match both named and anonymous structs … which it does by matching initializers, whose arguments are a tuple, so … turtles all the way down? More trouble.
• The existing unwrapping of single-elem tuples is preserved, i.e. `let a: (Int,Int) = (((((3))),3))` still works.

All of this would mean that this:

    enum Doodad
        {
        case Doo
        case Dad(name: String)
        }

…is equivalent to this (except for the introduction of the new “DadInfo” identifier for what was previously anonymous):

    struct DadInfo
        {
        var name: String
        }

    enum Doodad
        {
        case Doo
        case Dad(DadInfo)
        }

…and thus this is a non-breaking change:

    struct DadInfo
        {
        var name: String
        var punRating: JokeType = .Terrible
        }

    enum Doodad
        {
        case Doo
        case Dad(DadInfo)
        }

OK, so that’s an awful lot of squinting. It’s not clear that there’s an end game here that makes sense.

Cheers,

Paul

···

On Dec 22, 2015, at 12:03 PM, Chris Lattner <clattner@apple.com> wrote:

On Dec 22, 2015, at 9:44 AM, David Owens II via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 22, 2015, at 9:30 AM, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

Hmm, I wonder if #1 or #4 suggest any language changes to propose for this list.

Tangential to the topic at hand, but default values in tuples is something I’ve wanted for a while. Comparing optionals in a case statement would also be nice.

If you dig through history, you’ll find that we had them at one point (perhaps even in the swift 1.x timeframe), and we even had varargs tuples. We removed both of them because they were infrequently used and adding a tremendous amount of complexity to the compiler (and were very buggy as a result).


(Thorsten Seitz) #17

I find it a little bit strange that your error handling function takes an optional in the first place.
The caller should only call an errror handler in case of an error, so you should already have unwrapped the optional.

-Thorsten

···

Am 22.12.2015 um 18:30 schrieb Paul Cantrell via swift-evolution <swift-evolution@swift.org>:

func handleFileNotFound(error: ErrorType?)
        {
        if let error = error
            {
            if case MyError.FileNotFound(let url) = error
                { print(url) }
            }
        }


(Charles Srstka) #18

And my two cents is:

NSError is antiquated, and enums conforming to ErrorType, with associated types on the case values, are much better. They are cleaner, they are easier to use, and they are nicer to look at.

And I still think that’s a platform choice. If a platform wants to define it’s errors as a Swift enum, or if a developer is defining their own independent platform, that’s a semi-reasonable thing to do. But I think trying to force all the platforms (Apple and non-Apple) into an enum shaped box for cross platform Swift is going to be problematic. If the authors of Cocoa want to begin defining all their errors as enums, the ball is certainly in their court, but it doesn’t feel right to me to promote that up to a language concern.

Before multiplatform Swift, I was totally onboard with the idea of supercharging ErrorType, but the more I think about it, and the more I look at lower level stuff that we could begin using Swift for on other platforms, the more I think having larger error types needs to be a decision made by the host platform API on a case by case basis. And I’m not convinced an enum is an appropriate error tool for all cases, even though it is certainly appropriate for a lot of cases.

That’s a bit of a non-sequitur, though. Since ErrorType is a protocol, it can be anything. It can be an enum, a struct, a class, whatever you want. No one’s forcing enums, or anything really; enum just happens to be, in my opinion, the nicest of these to use. Someone else in the thread disagreed and preferred structs. That’s all fine; the whole point of ErrorType is that you can use what you want; there should just be a nicer way to bridge these things, in whatever form you prefer them, to NSError, so that we’re not forcing anyone into an NSError-shaped box.

I do think there are some weird sharp edges around NSError/ErrorType that aren’t specific to NSError, and will continue cutting people’s fingers on other platforms (for example, if I had a C API that defined it’s own error type as a struct, I’d be having a lot of the same problems.) I haven’t been able to come up with any reasonable ideas though that would fix everything.

One thing I think could be done is that NSError could somehow be made to conform to RawRepresentable, which would then take care of a lot of this.

The code could then become:
enum MyError: NSError {
    case JustFouledUp(domain: .., code: .., userInfo: ..)
    case CouldntDealWithFile(domain: .., code: .., userInfo: ..)
    case CouldntDealWithSomeValue(domain: .., code: .., userInfo: ..)
}

This is impossible, because enums with raw values can’t have cases with arguments. Try it: it won’t compile.

…with no need for the functions to implement to provide out the other usual data. I know some consider the NSError initializers kind of awful, that’s something that could be dealt with at the NSError API level. I actually think the above looks a lot cleaner, although I know I skipped over some of the mess with ellipsis. And API developers don’t have to implement manual, hand rolled bridging.

This seems cleanest to me, and would carry going forward with Error in OSS Foundation.

Honestly, I can’t imagine anyone *not* finding the NSError initializers awful. It’s a truly cringe-worthy API. Beyond that, forcing every error object, even ones that are never touching Obj-C code at all, to be backed by a bulky NSError object is pretty unreasonable, IMO. I’d much prefer to store the error data using lean and mean Swift-native constructs, and bridge to NSError only when it’s needed to interact with AppKit/UIKit.

Charles

···

On Dec 24, 2015, at 2:56 AM, Colin Cornaby <colin.cornaby@mac.com> wrote:


(Matthew Johnson) #19

Hmm, I wonder if #1 or #4 suggest any language changes to propose for this list.

Tangential to the topic at hand, but default values in tuples is something I’ve wanted for a while. Comparing optionals in a case statement would also be nice.

If you dig through history, you’ll find that we had them at one point (perhaps even in the swift 1.x timeframe), and we even had varargs tuples. We removed both of them because they were infrequently used and adding a tremendous amount of complexity to the compiler (and were very buggy as a result).

If you squint a little, tuples are just anonymous structs with limited features. If you squint.

One of my favorite features of Swift is how (at least at the semantic level) it has no primitive types. Int is a struct! Brilliant! I’ve wondered on and off (even before this list) if perhaps the language could eliminate another fundamental type by making tuples just be sugar for structs.

What would that look like? I am thinking out loud here…

Variadic generics would be a good start. See Tuple in C++.

I like that you address pattern matching for structs here as well.

···

Sent from my iPhone
On Dec 22, 2015, at 1:26 PM, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 22, 2015, at 12:03 PM, Chris Lattner <clattner@apple.com> wrote:

On Dec 22, 2015, at 9:44 AM, David Owens II via swift-evolution <swift-evolution@swift.org> wrote:
On Dec 22, 2015, at 9:30 AM, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

• Every tuple is secretly just an instance of an anonymous struct, much as optionals are secretly just enums.
• Under the covers, functions all take a single struct argument and return a single struct.
• Pattern matching can destructure structs. (Hmm, trouble here. Property ordering in structs is suddenly significant in the API, not just the ABI.)
• The tuple syntax infers the type of the struct to be created, and can match both named and anonymous structs … which it does by matching initializers, whose arguments are a tuple, so … turtles all the way down? More trouble.
• The existing unwrapping of single-elem tuples is preserved, i.e. `let a: (Int,Int) = (((((3))),3))` still works.

All of this would mean that this:

    enum Doodad
        {
        case Doo
        case Dad(name: String)
        }

…is equivalent to this (except for the introduction of the new “DadInfo” identifier for what was previously anonymous):

    struct DadInfo
        {
        var name: String
        }

    enum Doodad
        {
        case Doo
        case Dad(DadInfo)
        }

…and thus this is a non-breaking change:

    struct DadInfo
        {
        var name: String
        var punRating: JokeType = .Terrible
        }

    enum Doodad
        {
        case Doo
        case Dad(DadInfo)
        }

OK, so that’s an awful lot of squinting. It’s not clear that there’s an end game here that makes sense.

Cheers,

Paul

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Paul Cantrell) #20

Well … obviously. It’s a toy example. The point was the nested conditionals, not the method.

P

···

On Dec 23, 2015, at 12:12 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:

I find it a little bit strange that your error handling function takes an optional in the first place.
The caller should only call an errror handler in case of an error, so you should already have unwrapped the optional.

-Thorsten

Am 22.12.2015 um 18:30 schrieb Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

func handleFileNotFound(error: ErrorType?)
        {
        if let error = error
            {
            if case MyError.FileNotFound(let url) = error
                { print(url) }
            }
        }