> On Aug 29, 2016, at 11:14 AM, Joe Groff <[email protected]> wrote:
> 
>> 
>> On Aug 24, 2016, at 5:08 PM, Charles Srstka via swift-evolution 
>> <[email protected]> wrote:
>> 
>> MOTIVATION:
>> 
>> SE-0083 appears to be dead in the water, having been deferred until later in 
>> Swift 3 back in May and not having been heard from since then, with the 
>> Swift 3 release looming closer and closer. However, the predictability gains 
>> that would have been provided by this change remain desirable for cases 
>> where one needs to know the actual dynamic type of an entity before any 
>> bridging magic is involved. Additionally, performance-critical code may 
>> desire the ability to check something’s type quickly without incurring the 
>> overhead of Objective-C bridging code.
>> 
>> PROPOSED SOLUTION:
>> 
>> I propose the following operators: really_is, really_as, really_as?, and 
>> really_as!. These operators would only return a positive result if the type 
>> actually was what was being asked for, instead of something that might be 
>> able to bridge to that type.
>> 
>> DETAILED DESIGN:
>> 
>> let foo: Any = "Foo"
>> let bar: Any = NSString(string: "Bar")
>> 
>> let fooIsString = foo is String                  // true
>> let fooReallyIsString = foo really_is String     // true
>> 
>> let fooIsNSString = foo is NSString              // true
>> let fooReallyIsNSString = foo really_is NSString // false
>> 
>> let barIsString = bar is String                  // true
>> let barReallyIsString = bar really_is String     // false
>> 
>> let barIsNSString = bar is NSString              // true
>> let barReallyIsNSString = bar really_is NSString // true
>> 
>> ALTERNATIVES CONSIDERED:
>> 
>> Stick with using an unholy combination of Mirror and unsafeBitCast when you 
>> need to know what you’ve actually got.
> 
> It would be helpful to know why you want this. What are you trying to do?

Custom bridging behavior. My specific use case is obsolete since SE-0112, but 
before that I had my own little hacky SE-0112 implementation, that looked kind 
of like this:

protocol CSErrorType: ErrorType {
    var userInfo: [NSObject : AnyObject] { get }
    func toNSError() -> NSError
}

toNSError() had a default implementation that generated an appropriate NSError 
from the userInfo. This was pretty good, although I had to be diligent about 
always using .toNSError() rather than “as NSError”, so that the bridging 
behavior would be called. However, do/try/catch returns errors as ErrorType, 
which meant I had to do a dynamic check each time I caught an error, which was 
tedious. So, I decided to put an extension on ErrorType. The most convenient 
thing to do would be just to define a default implementation for toNSError() on 
ErrorType, but since there’s no way to make a method introduced by a protocol 
get dispatched dynamically, that would mean that CSErrorType’s implementation 
would never get called, even when it was appropriate. So I added a dynamic cast:

extension ErrorType {
    func toNSError() -> NSError {
        if let csError = self as? CSErrorType {
            return csError.toNSError()
        } else {
            return self as NSError
        }
    }
}

However, NSError conformed to ErrorType, and the code in “as NSError” 
apparently didn’t dynamically check the type of the error, so now if you called 
.toNSError() on a _SwiftNativeNSError that was statically typed as ErrorType, 
it would end up double-wrapped inside *another* _SwiftNativeNSError, which then 
prevented such errors from unwrapping back to Swift errors properly. So, my 
first thought was to just check for that:

extension ErrorType {
    func toNSError() -> NSError {
        if let ns = self as? NSError {
            return ns
        }

        if let csError = self as? CSErrorType {
            return csError.toNSError()
        } else {
            return self as NSError
        }
    }
}

But of course that will fail because “as? NSError” will always be true. Maybe 
“is” would be more honest?

extension ErrorType {
    func toNSError() -> NSError {
        if self is NSError {
            return unsafeBitCast(self, NSError.self)
        }

        if let csError = self as? CSErrorType {
            return csError.toNSError()
        } else {
            return self as NSError
        }
    }
}

Nope, of course “is NSError” is always true as well.

What I ended up doing was using a horrible little hack using Mirror that I’m 
really quite un-proud of to figure out if the thing was really an NSError or 
not. Knowing from this thread that “is” actually becomes honest when used on 
type(of:), I could have used that instead (yes, this example mixes Swift 2 and 
Swift 3, so I would have used “dynamicType” at the time; it’s just an example):

extension ErrorType {
    func toNSError() -> NSError {
        if type(of: self) is NSError.Type {
            return unsafeBitCast(self, NSError.self)
        }

        if let csError = self as? CSErrorType {
            return csError.toNSError()
        } else {
            return self as NSError
        }
    }
}

Unfortunately, the unsafeBitCast would probably have still been needed in order 
to avoid the bridging behavior when using “as!”.

While this particular use case isn’t really a “thing” anymore, someone else 
might have a different reason to bridge something in a custom way, and it’d be 
nice to have a way not to have to jump through all these hoops.

Charles

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to