Retroactive Conformances vs. Swift-in-the-OS

OK, I agree that having that as an annotation on protocol requirements would be generally useful.

To clarify, would this annotation only allow the customization point to be called by the implementation of the protocol but not in generic contexts constrained to the protocol or on existentials do the protocol? That would be really nice to have.

What scope would define the implementation of the protocol? The module in which it is declared?

Before diving too deep down this rabbithole, it might be a good idea to start a new thread about it.

1 Like

That's this, from the end:

  • "static ways": relying on a conformance to satisfy generic requirements / associated type requirements, or to convert a value with a concrete type to a value with protocol ("existential") type

  • "dynamic cast": checking whether a type conforms to a protocol at run time, via is or as? or as! or some other feature that has yet to be designed

I guess I can clarify that the former is considered a use at the point where the compiler would check it, i.e. wherever you call the generic function, form the bound generic type, or declare conformance to the protocol with constraints on its associated type.

1 Like

Apologies for the bump, but a recent thread reminded me of this issue: Foundation.Date conforming to Strideable iOS 13.

It has also come up a few more times since this thread:

I was wondering if it might be worth pitching some kind of warning/error for retroactive conformances involving resilient libraries.

3 Likes

Why isn't one option here allowing retroactive conformances to have an access level?

// Framework A
internal extension SomeStruct: CustomStringConvertible {
  var description: String {
    return "SomeStruct, via A"
  }
}
// Framework B
internal extension SomeStruct: CustomStringConvertible {
  var description: String {
    return "SomeStruct, via B"
  }
}
// main.swift
import A
import B
print(SomeStruct()) // Compiler error: type SomeStruct does not conform to protocol CustomStringConvertible
1 Like

There's some discussion of this in the (long) threads on Scoped Conformances - #2 by jrose and An Implementation Model for Rational Protocol Conformance Behavior, but regardless we'd still need to handle all the concerns raised in the "Co-existing conformances" section:

A couple of things I'd like to take issue with in this paragraph. They are perhaps subtle, but I think they are important.

First, it's certainly possible to ā€œsupport conflicting conformancesā€ in dynamic casts; you just need to define how the conflicts are handled. Once the semantics are written down, you can consider the conflicting conformances to be supported. Of course it's possible there are no semantics we can come up with that's both usable and implementable, but I doubt that's the case.

Secondly, I think it's dangerous to view almost anything in Swift as ā€œcompile-time only.ā€ At least in my experience, every time I've headed down that alley I find myself in a dead-end where something doesn't integrate with the rest of the language, which (in its most general expression) is highly dynamic. The simple inability to monomorphize Swift programs means that the universe of types is not known (in the general case) until runtime, and that in itself means that ā€œcompile-time only conformanceā€ is not really meaningful unless it's restricted to the monomorphizable subset of programs.

3 Likes

Does this same class of problems exist for adding functions or properties to standard library types via an extension?

Let's say that today I added this initializer to String via an extension.

extension String {

  /// Creates a `String` from a `StaticString`.
  public init(_ staticString: StaticString) {
    self = staticString.withUTF8Buffer { String(decoding: $0, as: UTF8.self) }
  }
}

Then in a future version of the standard library Apple added this initializer to the standard library.

Would Swift now not know which initializer to invoke when running my old app binary (compiled when this initializer did not exist in the standard library) on a new OS version (where this initializer exists in the standard library)?

1 Like

Nope, but that's because your version of this initializer is just a function namespaced to your module (its symbol would begin with $s11YourAppName...). If the standard library adds this method, it'll be mangled differently, with a standard-library specific mangling. Your binary will have been compiled to call into your app's symbol, so the new declaration will not affect it, but when you next recompile, you'll probably have to change your code.

This specific issue arises with conformances because they are unique at runtime, and are not namespaced to one or another module, because the compiler needs to look up the single conformance to a given type in order for dynamic dispatch to be deterministic.

12 Likes

@harlanhaskins thank you for the clear explanation :slight_smile:

1 Like