How to polyfill for older systems

I have a framework that had an Identifiable protocol, that more or less mimics what the new protocol in the standard library provides.
Now that I am using Swift 5.1 I am of course competing against the protocol from the standard library.
I can't just abandon my own protocol because I can't raise my deployment target to 10.15.

This is seriously so bad with ABI stability, that this question/problem will come up quite a bit I guess.

My framework is also used on the server with Linux, where ABI stability is not existent, so theoretically I could drop my own protocol there right away.

I could of course just shadow the protocol in my framework and use the fully qualified name with the type name wherever it is used:

In framework: protocol Identifiable {…}
and use everywhere: MyFramework.Identifiable
but that's not pretty as I want to use the official one, where supported.

Also are there problems with shadowing a protocol from the standard library?

This leaves me with the question:
How do I properly polyfill the protocol for all Apple platforms below the ones released in 2019 and use the "official" one from the standard library from then on (as well as always on Linux)?

4 Likes

Everything I've read so far seems to suggest the MO is "**** the older systems, pound sand.", roughly speaking.

I thought that additive standard library features would be back ported, not sure why that didn't happen with Identifiable.

I'm interested in learning what the right approach is here. I don't think conditional compilation would work here, because I think that would assume that the standard library and custom types are inter-compatible, which I don't think they are guaranteed to be (even with the same source code).

Yeah, it needs to be a runtime check for Apple systems. But that runtime check can be compile-time conditionally added for Apple systems only I guess.

I brought related topics up before:

and here

but I would like to reiterate on this issue as it is really pressing now with ABI stability, not only for SDK features anymore but actually standard library features.

1 Like

Another solution is to use typealias instead of Identifiable everywhere and select the right declaration by the language version.

Could you write out a sample of what you mean?

Again, it is not the language version that's the problem, but the OS version.
See the definition of Identifiable in the stdlib:

@available(OSX 10.15, iOS 13, tvOS 13, watchOS 6, *)
public protocol Identifiable {
…

Ah, sorry, my bad. I didn't notice you can't change deployment target, and I was suggesting to use #if statement for preprocessor around typealias declaration. It will not work in runtime with @available of course.

1 Like

This wouldn't be compatible between ABI boundaries, because the improvised protocol isn't guaranteed to be binary compatible with the system one.

It'd be nice if you could conform protocols to other protocols in an extension like this:

@available(OSX 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension Swift.Identifiable: MyFramework.Identifiable {}

Then everywhere you accept a MyFramework.Identifiable it would work with a Swift.Identifiable too.

1 Like

So my guess is then:

Every program that uses their own Identifiable protocol will now crash on macOS Catalina unless it is recompiled where all usage of the protocol is replaced with the fully qualified name?

That can't be right…

/cc @jrose

Even if the source doesn't qualify the type name, the mangled name used for runtime resolution does contain the module name. Compiled code using the same type name should not break. This is the same situation we had with the addition of Result.

5 Likes

Thank you @Avi! That makes total sense.

So still, does that mean that if I ever recompile, I have to hunt down every instance where I use my protocol and change that?

And now that I am already recompiling, is there no way to reasonably migrate to the official protocol where possible?

I believe the compiler will favor declarations from the current module. You would have issues only if a module was importing two other modules that each declare the type, where previously only one had it. So if your Identifiable protocol was declared in a different module that your app uses, you would have problems rebuilding the app after the standard library adds the same type name.

For what it's worth, I have the exact same issue with a type called Operation in a framework that interactions with a the Stellar blockchain. Within the SDK, I can use Operation without qualification, but users of the SDK have to qualify it if they import Foundation.

That's not the same though. My Identifiable is superseded by the official one (it is now basically a copy of the stdlib's protocol). So no one should need to prefix the protocol name anymore. (Well, that's my goal at least)

It's actually a bit stronger than this, for the standard library specifically. The compiler favors everything over the stdlib. So if you import Identifiable from some module, that version will continue to be used by the compiler, so there's no source breaking change, even without explicitly disambiguating. You can adopt the stdlib version at your leisure by removing the import and migrating to the new API as needed.

Note that this particular rule only holds for the standard library, not SDK frameworks, etc. (I think-- @Douglas_Gregor, can you confirm?)

1 Like

As I noted with my example with Operation, I know this is true, empirically.