Xcode 14 RC: "Cannot specialize protocol type"

It’s been tweaked on main!

2 Likes

That's right. Primary associated types were adopted in the standard library in SE-0358: Primary Associated Types in the Standard Library. Xcode 14 RC has the macOS 12.5 SDK, where this standard library change is not included.

To summarize, with the Swift 5.7 compiler you can:

  • Write your own protocols with primary associated types
  • Use primary associated type constraints for parameters and opaque return types
  • Write any types with primary associated type constraints.

For these generics features, the things where the Swift 5.7 runtime is required (and thus need to be deployment target or availability gated) are:

  • Dynamic casts to any types with primary associated type constraints
  • any types with primary associated type constraints used as generic arguments, e.g. Array<any Collection<Int>>

Both of these limitations can be worked around by writing your own AnyP<T> struct that operates on any P<T> in its implementation.

To write primary associated type constraints on the standard library protocols that have adopted primary associated types, e.g. Collection<Int>, you need to build against an SDK with the Swift 5.7 standard library, which for Apple platforms are the SDKs for iOS 16, iPadOS 16, tvOS 16, watchOS 9, and macOS 13.

15 Likes

I think it's important to to highlight that ultimately, this only means you cannot write an opaque return type of some Collection<Int> if you don't have such an SDK. Everything else that would use a primary associated type constraint on standard library protocols can either be written using the regular <C: Collection> where C.Element == Int generics syntax instead of some Collection<Int>, or using the type erasing wrapper structs like AnyCollection<Int> instead of any Collection<Int>.

9 Likes

Thank you Holly.

The consequences are much less dire than initially expected. Reverting some modern syntax involving Sequence, Collection, or Publisher, to classic where clauses is just a syntactic change, and it has no effect on the users of my library: the api of the library, my craft, is intact.

I'm still surprised that these limits have started to express themselves with Xcode 14 RC, when previous betas did not show anything wrong. Maybe this story can be reported to anyone working on the Xcode team as well.

I had the coldest of sweats.

OK. Now I have some code to ship.

11 Likes

This is iOS feature ?
How much do developers benefit from a stable ABI?

1 Like

Have you tried compiling for iOS? You shouldn’t need to change your code at all, because the iOS SDK bundled with Xcode 14 RC has the Swift 5.7 toolchain.

If you really need to support Xcode 14.0, you can revert to the old syntax. Or you can just take a dependency on whatever future Xcode ships the Swift 5.7 toolchain in all your relevant SDKs.

Yep, the problem only arises on the macOS and Mac Catalyst SDKs. Other SDKs are not affected.

If you really need to support Xcode 14.0, you can revert to the old syntax.

This is what I will do. Fortunately, I had no dependency on opaque return types (because the library has to run on operating systems that don't have this runtime feature).

Is there a way to conditionally compile for standard library 5.7? We have some APIs we want to expose that use Duration, and now that type is only visible to iOS and not macOS. The check for swift(>=5.7) will not work, and it doesn't seem that @available(SwiftStdLib 5.7, *) works outside the standard library. Is there some way we can conditionally ship this API for iOS and not macOS?

1 Like

I don't this this exist, there is no #if sdk(...) as far as I know. I'll let more knowledgeable people provide an accurate answer.

You can workaround by checking #if os(macOS) || targetEnvironment(macCatalyst) right now. Later, with the next Xcode release that comes with a new language version, you'll be able to switch to #if swift(>=5.8) or #if compiler(>=5.8), as a proxy for the stdlib version check we're after.

Don't ask me if I like this solution, the answer is obviously no. But it should work, along with a generous documentation comment for your future self.

2 Likes

People can get very creative when the language is lacking: https://twitter.com/qdoug/status/1567835049650790400

I'm speechless, because I know I don't have the patience and communication skills that could help the language designers who read this forums understand what people need and that maybe they have something to do.

1 Like

This is exactly the issue that prompted this recent discussion. It tends to come up every year around this time, and I post about it every year for the same reason.

So far, the best workaround I've found is #if canImport(SomeFrameworkIntroducedInTheNewSDK), but this only happens to work because there happens to be a new framework introduced in the new SDK.

5 Likes

Yes. Thanks for your patience and communication skills :-)

Now let's hope that the language designers come up with the idea that waiting for the community to leverage the evolution process and come up with a solution doesn't work here. What about a little maintenance release, from the most competent people, that fixes the old and known pain points? You know, something nice.

Doing nothing creates more, and more developers being hurt, until they internalize the pain (what has one to endure until one can write "as expected", in a perfect expression of the stockholm syndrome?)

1 Like

This used to be much less of a problem when the Swift runtime was shipped with apps. Feels like the whole stability thing (ABI and the language itself) has made things worse rather than better for developers.

(Also tying things to iOS releases is getting really irritating. eg SwiftUI refusing to backport anything)

8 Likes