Tuesday, August 29, 2017

Swift 4: Bridging Peephole for “as” Casts

John McCall (via Peter Steinberger):

Bridging conversions are not always desirable. First, they do impose some performance overhead which the user may not want. But they can also change semantics in unwanted ways. For example, in certain rare situations, the reference identity of an NSString return value is important — maybe it's actually a persistent NSMutableString which should be modified in-place, or maybe it's a subclass which carries additional information. A pair of bridging conversions from NSString to String and then back to NSString is likely to lose this reference identity. In the current representation, String can store an NSString reference, and if the String is bridged to NSString that reference will be used as the result; however, the bridging conversion from NSString does not directly store the original NSString in the String, but instead stores the result of invoking +copy on it, in an effort to protect against the original NSString being somehow mutable.

Bridging conversions arising from reasons #1 and #2 are avoidable, but bridging conversions arising from reason #3 currently cannot be eliminated without major inconvenience, such as writing a stub in Objective-C. This is unsatisfactory. At the same time, it is not valid for Swift to simply eliminate pairs of bridging conversions as a matter of course, precisely because those bridging conversions can be semantically important. We do not want optimization settings to be able to affect things as important as whether a particular NSString is mutable or not.

He proposes eliminating pairs of bridging conversions under certain circumstances:

This would avoid the bridging conversions through [View] on the return value of the getter:

let subviews = view.subviews as NSArray

This would not:

let subviews = view.subviews
let nsSubviews = subviews as NSArray

This would avoid the bridging conversion through [CIFilter] on the argument to the setter:

view.backgroundFilters = nsFilters as [CIFilter]

This would not:

let filters = nsFilters as [CIFilter]
view.backgroundFilters = filters

6 Comments RSS · Twitter

I don't like that proposal. It's way too subtle and easy to miss if there's not a comment going with it that explains the hidden behavior.

I am also surprised that an automatic type conversion would already happen during the "let x = view.subviews". I don't think that's how Swift works internally, or does it? I mean, no language should behave like that. Only when an assignment encounters an explicit type on the left side, the type conversion rules should apply, making his two alternatives behave the same, because after "let subviews = view.subviews" the subviews's type would still be the one returned by view.subviews, i.e. it would remain a NSArray, or Swift already create its own array container (using +copy) at that point? If that were indeed the case, then I think it would be smarter to introduce a new "sticky" attribute for types that says "avoid auto-conversion into Swift types", e.g. by writing "let subviews = view.subviews.keepOriginalType", so that now subviews has a type that says "I'm an ObjC type that shall not be auto-converted". Though, I realize, I am now mixing static (compile-time) and runtime behavior here.

Oh well, I guess this is the wrong place to discuss this anyway. I just hope that his proposal won't get accepted, although I agree with the need to do something about the problem he explains.

Oh wait - that's an Apple engineer?! So he has the power to implement this? And there has been no concerns raised? Oh, damn.

@Thomas He’s one of the key compiler guys. Super smart.

As I understand it, the root issue is that the type returned by view.subviews is not NSArray because Swift changes it to a Swift array when it imports the headers. If it didn't do that, you’d have to manually cast/convert at nearly every use. Auto-converting is a reasonable solution to that problem but needs an escape hatch for when performance or semantics really matter. I agree that the proposal is subtle. I like the idea but would prefer something more explicit.

[…] Swift.Codable, Swift 4: Key-Value Observation, Swift’s Error Handling Implementation, Swift 4: Bridging Peephole for “as” Casts, Swift 4: Synthesizing Equatable and Hashable Conformance, Swift 4: JSON With Encoder and […]

[…] method, which has a different signature and so bridges differently. But it seems to also work to disable the bridging by casting with as […]

Leave a Comment