SE-0364: Warning for Retroactive Conformances of External Types

This is a small but very important step in the right direction.

The source breaking potential of declaring retroactive conformances of foreign types is well illustrated in the proposal, but I'd like to point out an even deadlier potential problem: any binary that includes such conformances for ABI stable types may stop working when a future version of the corresponding library gains its own version of the same conformance.

We can easily end up in a situation where the executable is relying on the precise behavior of its own conformance, while it's also linking ABI stable OS components that are depending on the behavior of the newly introduced upstream implementation. The Swift Runtime needs to select exactly one conformance as active, and no matter which one it chooses, the executable will break, leading to a potentially unresolvable binary compatibility issue.

Here is a somewhat contrived example, for a conformance I'd love to add to the stdlib.

An app executable compiled with Swift 5.6 may include the following conformance:

extension String.Index: CustomStringConvertible {
    public var description: String { "\(encodedOffset)" }
}

Code in the same app may also depend on this exact implementation, for whatever reason:

let str = "\("hello".endIndex)" // "5"
let value = String(str, radix: 10)! // 5
...

A future version of the Swift Standard Library will (hopefully!) gain its own CustomStringConvertible conformance on String.Index:

// In the Standard Library:
@available(macOS x.y, iOS u.w, *)
extension String.Index: CustomStringConvertible {
    public var description: String {
      // Something returning a string of the form "5[utf8]"
    }
}

If this conformance takes precedence, the binary will no longer work:

let str = "\("hello".endIndex)" // "5[utf8]"
let value = String(str, radix: 10)! // 💥
...

If the app's conformance takes precedence, then similar issues may instead happen inside any OS component that gets loaded into the same process.

The upshot is that ABI stable modules (such as the Swift Standard Library) cannot currently introduce any new conformances to their own types without risking such issues. The risk varies greatly between particular conformances (it's probably not very high for the conformance above), but to some extent it's always there. It's also difficult to adequately estimate in advance; bincompat issues tend to pop up only after a change has been implemented. (Sadly, the risk increases directly with the desirability of the conformance: the more useful its addition would be, the more likely it is that apps would attempt to fill the gap on their own.)

This warning, as proposed, will not improve this situation at all. ABI stable modules will continue to be unable to add new conformances to their own types without risking binary compatibility breaks.

I think it would be helpful if we started to push back on such conformances now. Therefore, I believe it would be worth considering widening the scope of the proposal, by one or more of

  1. enabling the warning in all contexts,
  2. making it unconditional (while letting module maintainers declare a list of other modules that are allowed to add conformances), and/or
  3. turning it into an error in Swift 6 mode.

It's better doing this late than never, but the cat is out of the bag now -- we have about half a decade's worth of existing binaries with such conformances that we need to keep working, and the list grows larger every day.

That said, even without widening its scope, this warning will still be extremely helpful in preventing the accidental introduction of such conformances in ABI stable library code, helping to eliminate binary compatibility issues arising from such unintentional mistakes. This very much a worthy benefit on its own, if only of interest to people maintaining such libraries.

11 Likes