An Implementation Model for Rational Protocol Conformance Behavior

I hate to be the downer twice, but it's simply not feasible to capture all of a type's visible conformances at the point of calling a generic function. Look at Array: it already conforms to eighteen protocols with just the frameworks in Apple's SDKs. Having the compiler to build a table with dozens of additional members that may or may not be used is a waste of binary size (the conformance table for every concrete type passed to a generic function) and launch time (resolving references to conformances in other dylibs), and I suspect there'd be a run-time cost too but I can't pin it down at the moment.

More importantly, however, while we can certainly add things to the value witness table if it's valuable to do so, the value witness table is attached to the type metadata, and type metadata is unique. You cannot have an Array<Int> from one part of the program with different type metadata than an Array<Int> from another part of the program, and only the type metadata and requested conformances are passed to a generic function. Changing this behavior (say, by passing a conformance table with every generic parameter list) is in theory possible but would be an ABI break.


I think there's just a general problem here regarding mixing dynamic casts with retroactive conformances. If you don't have retroactive conformances, there are only ever two places to search for conformances: "next to" the type, and "next to" the protocol. However, Objective-C has unconstrained retroactive conformances, and so Swift got them too, even though Swift's conformance model is different than Objective-C's.* And there are plenty of uses for retroactive conformances (which were pointed out to me when I tried and failed to restrict them). But I remain convinced that the answer is going to be changing the behavior of dynamic casts rather than the behavior of conformances.

I'm being all gloomy without proposing alternatives, but I don't have any great ones. Explicitly listing the "optional conformances" that would be checked dynamically is really no different than overloading, although it's a little more succint. Another alternative would be to change casting behavior so that it builds a conformance table at the location of the cast, and only checks those conformances (in addition to the ones in the type's module and the protocol's module). But I don't think that has any of the behavior you want.

This is just a hard problem. as? exists as a global search for conformances, and changing that will almost certainly break existing apps even if we wish it wouldn't. And we didn't even touch existentials in this discussion, which really can't take a context-specific conformance table either (especially for classes going through an AnyObject representation). But I really think the "irrational behavior" is on the part of as?, not conformances, and I don't think that all generic functions should pay a price so that as? gets more sensible behavior.

* In Objective-C, a conformance is "just" a promise to implement certain methods, which are looked up by selector like all other Objective-C methods. So retroactive conformances are no more dangerous a feature than adding your own methods to an existing class, though I suppose you're more likely to collide with someone else's method, in which case "last one loaded wins".

6 Likes