[Accepted] SE-0112: Improved NSError Bridging

An immediate problem I’m seeing is the error: using 'Error' as a concrete type conforming to protocol 'Error' is not supported, which means we can’t use Error in our Result or Response types, as both the value and error types are generic there. I’m guessing we’ll have to either remove the generic error type, which would greatly impact consumer’s ability to use our APIs with their own custom errors, or wrap all errors in our own error type, which is terrible for a few reasons. Or just keep using NSError I guess. Any clever solutions here?

Jon

···

On Aug 14, 2016, at 1:41 AM, Jon Shier <jon@jonshier.com> wrote:

Doug, et. al.:
  Thanks for the discussion so far; I think I understand the new error model from a user’s perspective. However, now I’m looking for some guidance for framework developers. While the proposal laid out the system types would wrap imported Objective-C errors, I don’t see anything for the desired patters for Swift native errors. For instance, in Alamofire we currently either pass through the NSErrors we receive from underlying frameworks or generate our own, usually in our own domain, with unique error codes and such. I can see my way to translating most of these directly to an AFError type that conforms to Error, but some of them require associated values (e.g. the content type validation error needs to know the expected and actual content types). In these cases I’m having a hard time see how these things should be stored, especially when only some cases need this data. Of course, I’m away of the LocalizedError protocol, but that doesn’t seem applicable here, as I’m mostly wondering about storage. Rather than creating types similar to the system error types, perhaps a basic enum based error would work, where only the cases that need it capture values? I’m mostly curious what the anticipated pattern was here.

Jon

On Aug 6, 2016, at 1:15 AM, Kevin Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Aug 5, 2016, at 7:36 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

On Aug 5, 2016, at 8:10 PM, Kevin Ballard <kevin@sb.org <mailto:kevin@sb.org>> wrote:

On Aug 5, 2016, at 5:16 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

On Aug 5, 2016, at 4:19 PM, Douglas Gregor via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Aug 5, 2016, at 12:59 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

If all you want to do is get the localized description, then you can just say `(error as NSError).localizedDescription`.

Just ‘error.localizedDescription’ works now. That was part of SE-0112.

  - Doug

Would it kill to allow:

let err = NSError()
err.localizedDescription = "bad things happen"
throw err

or even

throw NSError("Bad things happen")

for lightweight use? I ended up refactoring entirely to enum : Error because Swift yelled at me for using NSError(): "this results in an invalid NSError instance. It will raise an exception in a future release. Please call errorWithDomain:code:userInfo: or initWithDomain:code:userInfo:. This message shown only once."

enum Errors: Error {case bad}
Errors.bad._code // 0
Errors.bad._domain // "Errors"
Errors.bad._userInfo // Optional({})
Errors.bad.localizedDescription // "The operation couldn’t be completed. (Errors error 0.)"

Bleh.

NSErrors need a domain/code. It doesn’t make much sense to throw one without it. And besides, there’s a fairly trivial solution for doing what you want to do:

struct GenericError: LocalizedError {
    let message: String
    init(_ message: String) {
        self.message = message
    }
    var errorDescription: String? {
        return message
    }
}

Now you can just say `throw GenericError(“Bad things happen”)`.

-Kevin Ballard

I know I can build workarounds but if we're going to have the error.localizedDescription, making it an initializable/assignable property just seems like a nice thing™. Why can't we have nice things™?

I don’t actually think it’s a nice thing™ to have it be assignable like you ask, because we should be encouraging people to use typed errors. You may as well just ask for String to conform to Error (in fact, you could just add that conformance yourself and skip the GenericError wrapper entirely).

-Kevin
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Works fine when I try it:

import Foundation

struct MyThing {
    enum Error: Swift.Error, LocalizedError {
        case doesNotCompute
        case imSorryDave
        case mustSterilize
        
        var failureReason: String? {
            switch self {
            case .doesNotCompute:
                return "Does Not Compute! Does Not Compute! Does Not Compute!"
            case .imSorryDave:
                return "I'm sorry Dave, I'm afraid I can't do that."
            case .mustSterilize:
                return "Error! Must Sterilize! Must Steeerrrrilllliiiiiiizzzzzzeeeeeee"
            }
        }
    }
    
    func trySomething() throws {
        throw Error.doesNotCompute
    }
}

let thing = MyThing()

do {
    try thing.trySomething()
} catch {
    print(error.localizedDescription)
}

Outputs:

The operation couldn’t be completed. Does Not Compute! Does Not Compute! Does Not Compute!

Charles

···

On Aug 14, 2016, at 1:18 AM, Jon Shier via swift-evolution <swift-evolution@swift.org> wrote:

  An immediate problem I’m seeing is the error: using 'Error' as a concrete type conforming to protocol 'Error' is not supported, which means we can’t use Error in our Result or Response types, as both the value and error types are generic there. I’m guessing we’ll have to either remove the generic error type, which would greatly impact consumer’s ability to use our APIs with their own custom errors, or wrap all errors in our own error type, which is terrible for a few reasons. Or just keep using NSError I guess. Any clever solutions here?

Sorry Charles, I should’ve been more specific. This isn’t about some other type named Error being usable, but about our doubly generic Result and other types being usable with Error as the generic error type. If we have Result defined as:

public enum Result<Value, ErrorType: Error> {
    case success(Value)
    case failure(ErrorType)
}

then attempting to create the type Result<Whatever, Error> results in the error message I posted. For methods that previously returned Result<Whatever, NSError>, where the NSError is either a system error or an Alamofire one, this is something of a problem. Of course, removing the generic error type fixes this but may have an undesirable usability impact, as making Result double generic was the primary reason behind the quick upgrade between Alamofire 2 and 3. I think it’s less of a problem now, as having to enumerate all of the different expected error types is how user’s are supposed to interact with Error in the first place, but at the time it wasn’t desirable to try and force everything through ErrorProtocol for that reason. Perhaps this is a good compromise, where instead of returning all NSErrors from the framework and allowing consumers to add their own to the mix we’ll just create our own AFError type (whether enum or struct) and then anything coming back will have to be checked for that type in addition to the system types. From my testing it looks like users could still wrap everything coming in by capturing the underlying Error.

Jon

···

On Aug 14, 2016, at 3:03 AM, Charles Srstka <cocoadev@charlessoft.com> wrote:

On Aug 14, 2016, at 1:18 AM, Jon Shier via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

  An immediate problem I’m seeing is the error: using 'Error' as a concrete type conforming to protocol 'Error' is not supported, which means we can’t use Error in our Result or Response types, as both the value and error types are generic there. I’m guessing we’ll have to either remove the generic error type, which would greatly impact consumer’s ability to use our APIs with their own custom errors, or wrap all errors in our own error type, which is terrible for a few reasons. Or just keep using NSError I guess. Any clever solutions here?

Works fine when I try it:

import Foundation

struct MyThing {
    enum Error: Swift.Error, LocalizedError {
        case doesNotCompute
        case imSorryDave
        case mustSterilize
        
        var failureReason: String? {
            switch self {
            case .doesNotCompute:
                return "Does Not Compute! Does Not Compute! Does Not Compute!"
            case .imSorryDave:
                return "I'm sorry Dave, I'm afraid I can't do that."
            case .mustSterilize:
                return "Error! Must Sterilize! Must Steeerrrrilllliiiiiiizzzzzzeeeeeee"
            }
        }
    }
    
    func trySomething() throws {
        throw Error.doesNotCompute
    }
}

let thing = MyThing()

do {
    try thing.trySomething()
} catch {
    print(error.localizedDescription)
}

Outputs:

The operation couldn’t be completed. Does Not Compute! Does Not Compute! Does Not Compute!

Charles

Still seems to be working well, unless I’m misunderstanding what you’re trying to do:

import Foundation

enum Result<Value, ErrorType: Error> {
    case success(Value)
    case failure(ErrorType)
}

struct MyThing {
    enum Error: Swift.Error, LocalizedError {
        case doesNotCompute
        case imSorryDave
        case mustSterilize
        case irrelevant
        case endOfLine
        
        var failureReason: String? {
            switch self {
            case .doesNotCompute:
                return "Does Not Compute! Does Not Compute! Does Not Compute!"
            case .imSorryDave:
                return "I'm sorry Dave, I'm afraid I can't do that."
            case .mustSterilize:
                return "Error! Must Sterilize! Must Steeerrrrilllliiiiiiizzzzzzeeeeeee"
            case .irrelevant:
                return "Irrelevant. Resistance is futile."
            case .endOfLine:
                return "End of Line!"
            }
        }
    }
    
    func trySomething(shouldWork: Bool, completionHandler: (Result<String, Error>) -> ()) {
        if shouldWork {
            completionHandler(.success("It worked!"))
        } else {
            completionHandler(.failure(Error.imSorryDave))
        }
    }
}

let thing = MyThing()

let completionHandler = { (result: Result<String, MyThing.Error>) in
    switch result {
    case let .success(value):
        print("returned '\(value)'")
    case let .failure(error):
        print("error: \(error.localizedDescription)")
    }
}

thing.trySomething(shouldWork: true, completionHandler: completionHandler)
thing.trySomething(shouldWork: false, completionHandler: completionHandler)

returned 'It worked!'
error: The operation couldn’t be completed. I'm sorry Dave, I'm afraid I can't do that.
Program ended with exit code: 0

Charles

···

On Aug 14, 2016, at 2:34 AM, Jon Shier <jon@jonshier.com> wrote:

  Sorry Charles, I should’ve been more specific. This isn’t about some other type named Error being usable, but about our doubly generic Result and other types being usable with Error as the generic error type. If we have Result defined as:

public enum Result<Value, ErrorType: Error> {
    case success(Value)
    case failure(ErrorType)
}

then attempting to create the type Result<Whatever, Error> results in the error message I posted. For methods that previously returned Result<Whatever, NSError>, where the NSError is either a system error or an Alamofire one, this is something of a problem. Of course, removing the generic error type fixes this but may have an undesirable usability impact, as making Result double generic was the primary reason behind the quick upgrade between Alamofire 2 and 3. I think it’s less of a problem now, as having to enumerate all of the different expected error types is how user’s are supposed to interact with Error in the first place, but at the time it wasn’t desirable to try and force everything through ErrorProtocol for that reason. Perhaps this is a good compromise, where instead of returning all NSErrors from the framework and allowing consumers to add their own to the mix we’ll just create our own AFError type (whether enum or struct) and then anything coming back will have to be checked for that type in addition to the system types. From my testing it looks like users could still wrap everything coming in by capturing the underlying Error.

Yes, if you return a single error type it works fine. However, since Alamofire wraps the underlying networking frameworks there’s a need to return the errors returned from them as well, which are now returned as Error. In order to let these errors be returned alongside Alamofire’s errors we’d either have to get rid of the generic error and just use Error or wrap all errors in our error type. So far the least painful method seems to be just getting rid of the generic error and returning Error for everything. That way users will use the same logic they would have had to use anyway and can also provide their own error wrapper if they want. Thankfully (or unfortunately, depending on your point of view), the new pattern is to cast out the various error types, so the previous concern about having to do that is no longer an issue.

Jon

···

On Aug 14, 2016, at 4:18 AM, Charles Srstka <cocoadev@charlessoft.com> wrote:

On Aug 14, 2016, at 2:34 AM, Jon Shier <jon@jonshier.com <mailto:jon@jonshier.com>> wrote:

  Sorry Charles, I should’ve been more specific. This isn’t about some other type named Error being usable, but about our doubly generic Result and other types being usable with Error as the generic error type. If we have Result defined as:

public enum Result<Value, ErrorType: Error> {
    case success(Value)
    case failure(ErrorType)
}

then attempting to create the type Result<Whatever, Error> results in the error message I posted. For methods that previously returned Result<Whatever, NSError>, where the NSError is either a system error or an Alamofire one, this is something of a problem. Of course, removing the generic error type fixes this but may have an undesirable usability impact, as making Result double generic was the primary reason behind the quick upgrade between Alamofire 2 and 3. I think it’s less of a problem now, as having to enumerate all of the different expected error types is how user’s are supposed to interact with Error in the first place, but at the time it wasn’t desirable to try and force everything through ErrorProtocol for that reason. Perhaps this is a good compromise, where instead of returning all NSErrors from the framework and allowing consumers to add their own to the mix we’ll just create our own AFError type (whether enum or struct) and then anything coming back will have to be checked for that type in addition to the system types. From my testing it looks like users could still wrap everything coming in by capturing the underlying Error.

Still seems to be working well, unless I’m misunderstanding what you’re trying to do:

import Foundation

enum Result<Value, ErrorType: Error> {
    case success(Value)
    case failure(ErrorType)
}

struct MyThing {
    enum Error: Swift.Error, LocalizedError {
        case doesNotCompute
        case imSorryDave
        case mustSterilize
        case irrelevant
        case endOfLine
        
        var failureReason: String? {
            switch self {
            case .doesNotCompute:
                return "Does Not Compute! Does Not Compute! Does Not Compute!"
            case .imSorryDave:
                return "I'm sorry Dave, I'm afraid I can't do that."
            case .mustSterilize:
                return "Error! Must Sterilize! Must Steeerrrrilllliiiiiiizzzzzzeeeeeee"
            case .irrelevant:
                return "Irrelevant. Resistance is futile."
            case .endOfLine:
                return "End of Line!"
            }
        }
    }
    
    func trySomething(shouldWork: Bool, completionHandler: (Result<String, Error>) -> ()) {
        if shouldWork {
            completionHandler(.success("It worked!"))
        } else {
            completionHandler(.failure(Error.imSorryDave))
        }
    }
}

let thing = MyThing()

let completionHandler = { (result: Result<String, MyThing.Error>) in
    switch result {
    case let .success(value):
        print("returned '\(value)'")
    case let .failure(error):
        print("error: \(error.localizedDescription)")
    }
}

thing.trySomething(shouldWork: true, completionHandler: completionHandler)
thing.trySomething(shouldWork: false, completionHandler: completionHandler)

returned 'It worked!'
error: The operation couldn’t be completed. I'm sorry Dave, I'm afraid I can't do that.
Program ended with exit code: 0

Charles

My 2 cents:

Generic errors have caused me problems on multiple occasions.They often make it difficult to handle and pass-through arbitrary errors. This pseudo Swift3 code is what I have resorted to doing on some projects.

enum MyError : Error
{
	…

	case wrappedError( Error )
}

I would go so far as to say that generic errors are an anti-pattern in Swift.

- Chris

···

On Aug 14, 2016, at 6:04 PM, Jon Shier via swift-evolution <swift-evolution@swift.org> wrote:

  Yes, if you return a single error type it works fine. However, since Alamofire wraps the underlying networking frameworks there’s a need to return the errors returned from them as well, which are now returned as Error. In order to let these errors be returned alongside Alamofire’s errors we’d either have to get rid of the generic error and just use Error or wrap all errors in our error type. So far the least painful method seems to be just getting rid of the generic error and returning Error for everything. That way users will use the same logic they would have had to use anyway and can also provide their own error wrapper if they want. Thankfully (or unfortunately, depending on your point of view), the new pattern is to cast out the various error types, so the previous concern about having to do that is no longer an issue.

Jon

On Aug 14, 2016, at 4:18 AM, Charles Srstka <cocoadev@charlessoft.com <mailto:cocoadev@charlessoft.com>> wrote:

On Aug 14, 2016, at 2:34 AM, Jon Shier <jon@jonshier.com <mailto:jon@jonshier.com>> wrote:

  Sorry Charles, I should’ve been more specific. This isn’t about some other type named Error being usable, but about our doubly generic Result and other types being usable with Error as the generic error type. If we have Result defined as:

public enum Result<Value, ErrorType: Error> {
    case success(Value)
    case failure(ErrorType)
}

then attempting to create the type Result<Whatever, Error> results in the error message I posted. For methods that previously returned Result<Whatever, NSError>, where the NSError is either a system error or an Alamofire one, this is something of a problem. Of course, removing the generic error type fixes this but may have an undesirable usability impact, as making Result double generic was the primary reason behind the quick upgrade between Alamofire 2 and 3. I think it’s less of a problem now, as having to enumerate all of the different expected error types is how user’s are supposed to interact with Error in the first place, but at the time it wasn’t desirable to try and force everything through ErrorProtocol for that reason. Perhaps this is a good compromise, where instead of returning all NSErrors from the framework and allowing consumers to add their own to the mix we’ll just create our own AFError type (whether enum or struct) and then anything coming back will have to be checked for that type in addition to the system types. From my testing it looks like users could still wrap everything coming in by capturing the underlying Error.

Still seems to be working well, unless I’m misunderstanding what you’re trying to do:

import Foundation

enum Result<Value, ErrorType: Error> {
    case success(Value)
    case failure(ErrorType)
}

struct MyThing {
    enum Error: Swift.Error, LocalizedError {
        case doesNotCompute
        case imSorryDave
        case mustSterilize
        case irrelevant
        case endOfLine
        
        var failureReason: String? {
            switch self {
            case .doesNotCompute:
                return "Does Not Compute! Does Not Compute! Does Not Compute!"
            case .imSorryDave:
                return "I'm sorry Dave, I'm afraid I can't do that."
            case .mustSterilize:
                return "Error! Must Sterilize! Must Steeerrrrilllliiiiiiizzzzzzeeeeeee"
            case .irrelevant:
                return "Irrelevant. Resistance is futile."
            case .endOfLine:
                return "End of Line!"
            }
        }
    }
    
    func trySomething(shouldWork: Bool, completionHandler: (Result<String, Error>) -> ()) {
        if shouldWork {
            completionHandler(.success("It worked!"))
        } else {
            completionHandler(.failure(Error.imSorryDave))
        }
    }
}

let thing = MyThing()

let completionHandler = { (result: Result<String, MyThing.Error>) in
    switch result {
    case let .success(value):
        print("returned '\(value)'")
    case let .failure(error):
        print("error: \(error.localizedDescription)")
    }
}

thing.trySomething(shouldWork: true, completionHandler: completionHandler)
thing.trySomething(shouldWork: false, completionHandler: completionHandler)

returned 'It worked!'
error: The operation couldn’t be completed. I'm sorry Dave, I'm afraid I can't do that.
Program ended with exit code: 0

Charles

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

Is there something wrong with just returning a Swift.Error and using casting to catch specific errors?

import Foundation

enum Result<Value> {
    case success(Value)
    case failure(Swift.Error)
}

struct StarTrek {
    enum Error: Swift.Error, LocalizedError {
        case insufficientData
        case sheCannaTakeIt
        case warpCoreBreach(minutes: Int)
        case irrelevant
        case mustSterilize
        
        var failureReason: String? {
            switch self {
            case .insufficientData:
                return "Insufficient data. Please specify parameters."
            case .sheCannaTakeIt:
                return "She canna take it anymore, Captain!"
            case let .warpCoreBreach(minutes):
                return "Warning: Warp core breach in \(minutes) minutes."
            case .mustSterilize:
                return "Error! Must Sterilize! Must Steeerrrrilllliiiiiiizzzzzzeeeeeee"
            case .irrelevant:
                return "Irrelevant. Resistance is futile."
            }
        }
    }
    
    static func engage(warpFactor: Int) throws {
        throw Error.sheCannaTakeIt
    }
}

struct SciFi {
    enum Error: Swift.Error, LocalizedError {
        case doesNotCompute
        case imSorryDave
        case fixedPointInTime
        case gameOver
        case endOfLine
        
        var failureReason: String? {
            switch self {
            case .doesNotCompute:
                return "Does Not Compute! Does Not Compute! Does Not Compute!"
            case .imSorryDave:
                return "I'm sorry Dave, I'm afraid I can't do that."
            case .fixedPointInTime:
                return "I'm sorry, I'm so sorry."
            case .gameOver:
                return "Game over man, game over!"
            case .endOfLine:
                return "End of Line!"
            }
        }
    }
    
    static func flyThroughSpace(isStarTrek: Bool, completionHandler: (Result<String>) -> ()) {
        if isStarTrek {
            do {
                try StarTrek.engage(warpFactor: 5)
                completionHandler(.success("We have arrived at Rigel VII"))
            } catch {
                completionHandler(.failure(error))
            }
        } else {
            completionHandler(.failure(Error.imSorryDave))
        }
    }
}

let completionHandler = { (result: Result<String>) in
    switch result {
    case let .success(value):
        print("returned '\(value)'")
    case let .failure(error):
        if let starTrekError = error as? StarTrek.Error {
            print("Star Trek error: \(starTrekError.localizedDescription)")
            
            if case .sheCannaTakeIt = starTrekError {
                print("Scotty, I need more power!")
            }
        } else if let scifiError = error as? SciFi.Error {
            print("Sci fi error: \(scifiError.localizedDescription)")
            
            if scifiError == .imSorryDave {
                print("Daisy... Daaaaaaiiiiiisssssyyyyy.........")
            }
        } else {
            print("Some other error: \(error.localizedDescription)")
        }
    }
}

SciFi.flyThroughSpace(isStarTrek: true, completionHandler: completionHandler)
SciFi.flyThroughSpace(isStarTrek: false, completionHandler: completionHandler)

Star Trek error: The operation couldn’t be completed. She canna take it anymore, Captain!
Scotty, I need more power!
Sci fi error: The operation couldn’t be completed. I'm sorry Dave, I'm afraid I can't do that.
Daisy... Daaaaaaiiiiiisssssyyyyy.........
Program ended with exit code: 0

And of course, for synchronous APIs using try/catch, you can just use “catch error as StarTrek.Error” to get these.

Charles

···

On Aug 14, 2016, at 7:04 PM, Jon Shier <jon@jonshier.com> wrote:

  Yes, if you return a single error type it works fine. However, since Alamofire wraps the underlying networking frameworks there’s a need to return the errors returned from them as well, which are now returned as Error. In order to let these errors be returned alongside Alamofire’s errors we’d either have to get rid of the generic error and just use Error or wrap all errors in our error type. So far the least painful method seems to be just getting rid of the generic error and returning Error for everything. That way users will use the same logic they would have had to use anyway and can also provide their own error wrapper if they want. Thankfully (or unfortunately, depending on your point of view), the new pattern is to cast out the various error types, so the previous concern about having to do that is no longer an issue.

Generic errors have caused me problems on multiple occasions.They often make it difficult to handle and pass-through arbitrary errors. This pseudo Swift3 code is what I have resorted to doing on some projects.

enum MyError : Error
{
	…

	case wrappedError( Error )
}

I would go so far as to say that generic errors are an anti-pattern in Swift.

  I wouldn’t call them an anti pattern, but they do force the user to make a decision about how they’re handling errors. Either everything is wrapped in their own custom error type at some point, or they just return NSErrors for everything. I preferred wrapping everything in my own type so handling was centralized and I knew my code exhaustively handled all cases. Of course I never used to the more advanced features of NSError. It certainly looks like this has to change now.

···

On Aug 14, 2016, at 9:43 PM, Charles Srstka <cocoadev@charlessoft.com> wrote:

Is there something wrong with just returning a Swift.Error and using casting to catch specific errors?

  Fundamentally, I think there is, as it requires users know every type of error that could possibly be returned by an API to handle them exhaustively, or for users to create some sort of default behavior that may not be optimal or safe in all cases. Unfortunately I understand now that it’s largely unavoidable given the nature of NSError and error handling in Apple’s frameworks in general. So I’ve accepted that’s how we have to do it from now on. My recent questions, however, were merely seeking guidance as a framework developer. I’ve come to the conclusion that there isn’t really a better solution for Alamofire than removing the generic error parameter from the relevant types and letting Error bubble up from the underlying frameworks. Users can still wrap everything in a custom type that captures the underlying errors or handle everything manually. I guess there is no clever solution here.

Thanks everyone,

Jon

Why do you need to exhaustively handle every type of error that could occur? Don’t you only need to know about the small subset of errors that you know you can recover from, and for the rest, bail and pass the error on down the chain to whatever ultimately ends up presenting the error?

Charles

···

On Aug 15, 2016, at 12:12 AM, Jon Shier <jon@jonshier.com> wrote:

On Aug 14, 2016, at 9:43 PM, Charles Srstka <cocoadev@charlessoft.com <mailto:cocoadev@charlessoft.com>> wrote:

Is there something wrong with just returning a Swift.Error and using casting to catch specific errors?

  Fundamentally, I think there is, as it requires users know every type of error that could possibly be returned by an API to handle them exhaustively, or for users to create some sort of default behavior that may not be optimal or safe in all cases. Unfortunately I understand now that it’s largely unavoidable given the nature of NSError and error handling in Apple’s frameworks in general. So I’ve accepted that’s how we have to do it from now on. My recent questions, however, were merely seeking guidance as a framework developer. I’ve come to the conclusion that there isn’t really a better solution for Alamofire than removing the generic error parameter from the relevant types and letting Error bubble up from the underlying frameworks. Users can still wrap everything in a custom type that captures the underlying errors or handle everything manually. I guess there is no clever solution here.