Explicitly defined type casting?

If I have code like:

if (abc as? MySpecialType) == .mySpecialValue {
   ...
}

Is there a way to extend either the type of "abc" or MySpecialType so that it understands how to do this conversion?

My specific use-case is that I'm defining a swift error type (enum conforming to LocalizedError), but this is in the middle of some low-level network stack, which ends up getting converted to an NSURLError to the application (I have no control over this). So when I try to do as?, it always returns false. Instead, I'm forced initialize like so:

if MySpecialFailure(error: error) == . failureCase1 {
    ...
}

With a bit of an ugly initializer like this:

MySpecialFailure: LocalizedError {
    case failureCase1
    case failureCase2
    
    public init?(error: Error) {
        if let failure = error as? MySpecialFailure {
            self = failure
        } else if let failure = MySpecialFailure(error: error as NSError) {
            self = failure
        } else {
            return nil
        }
    }
    
    public init?(error: NSError) {
        let convertableFailures: [MySpecialFailure] = [. failureCase1, . failureCase2]
        for failure in convertableFailures {
            let nsError = failure as NSError
            if error.domain == nsError.domain,
               error.code == nsError.code
            {
                self = failure
                return
            }
        }
        return nil
    }

Any thoughts on a better approach? To reiterate, ideally I'd like this to work:

if (error as? MySpecialFailure) == .failureCase1 {
   ...
}

Where "error" is an NSError.

I am not sure this helps, but you could use the following initialiser implementation

enum MySpecialFailure: LocalizedError {
    case failureCase1
    case failureCase2
    
    private init?(domain: String, code: Int) {
        switch (domain, code) {
        case ("failure 1", 10):
            self = .failureCase1
        case ("failure 2", 20):
            self = .failureCase2
        default:
            return nil
        }
    }
    
    init?(error: NSError) {
        self.init(domain: error.domain, code: error.code)
    }
}

if MySpecialFailure(error: error) == .failureCase1 {
    print("matches")
}
1 Like

No, there is no public feature in Swift to customize the behavior of as?—the use of a failable converting initializer as you've done here is essentially how this operation is expected to be spelled (though the API guidelines would tell you to drop the label error:); @somu shows one way how one might simplify the implementation with a switch statement.

1 Like

Thanks, I figured as much, just wanted to confirm. This is one of those weird cases I think where a Swift Error object is converted to an Objective-C NSError object (by the underlying stack), but then returned back to a Swift function where I actually want the original Swift Error object.

Thanks. The reason I opted for the loop rather than an explicit switch as you have here is because I actually don't know what the "code" values should be. I believe it's automatically converted in order, such that "failureCase1" has a code of 1, "failureCase2" has a code of 2, etc. But I did not want to rely on that. In addition it would be a hard bug to catch if someone were to reorder the enum.

In addition, "domain" appears to be autogenerated as a combination of "SPM Package Name.Library Name". So I also did not want to make any assumptions that that implementation would change either.