Removing mutation modifiers in Codable

In Codable.swift, a lot of functions declared in protocols and types are marked with mutating modifier. However, the implementation is such that modifying structs implementing these protocols will have no effect. In particular:

  • KeyedEncodingContainerProtocol. The field var concrete of the _KeyedEncodingContainerBox class uses KeyedEncodingContainerProtocol , but the encode functions mutate the field without writing the updated value anywhere. This makes it impossible to implement a struct conforming to the KeyedEncodingContainerProtocol. It has to be a class or a struct that proxies encode calls to a class. The mutation modifiers are therefore unnecessary and misleading. The concrete property will become let.
  • Since KeyedEncodingContainer functions operate on the _box, which is a reference-typed property, they do not need to be marked as mutating. Additionally, the _box property should be declared as let instead of var.
  • The variables _box and concrete in KeyedDecodingContainer and _KeyedDecodingContainerBox, respectively, should also be declared as let.
  • UnkeyedEncodingContainer and SingleValueEncodingContainer also have no benefits from having mutating modifiers. They are used as temporary variables in encode(to:) functions and never written anywhere.

Unfortunately removing the mutating modifier will break source compatibility. However, this issue could be addressed in Swift 6.

Would this only be a source break, or would it break ABI?

Adding or removing ‘mutating’ is an ABI break.

3 Likes

Is there a plan to introduce ABI-breaking changes in Swift 6?

No, my understanding is that Swift 5 -> 6 will be the first (intentional) source-break that does not come with an ABI break. Code written in Swift 5 must, at least on Apple’s platforms, continue to work with the Swift 6 standard library.

1 Like

While this is true in the sense that Swift's ABI was not stable anywhere prior to Swift 5, it is also worth clarification that the lack of an ABI break in Swift 6 isn't 'just so,' i.e., something that might be different for Swift X. Proper ABI breaks on the ABI-stable platforms should be considered entirely off the table.

For the right fix/feature it might make sense to go to heroic implementation lengths to maintain ABI compatibility for what would otherwise be an ABI-breaking change, but the bar would be quite high for justifying such investment.

6 Likes

Does this mean that any imperfections in the stdlib that require ABI-breaking changes to fix will not be addressed in the future? Or maybe there is a plan for a future swift version to support multiple versions of ABI in one package?

An imperfection can always be addressed by introducing new declarations. For example if Collection is found to be broken beyond repair (which is unlikely so say the least), we can introduce a new Collection2 protocol or whatever. This is how Java evolved over the decades, by deprecating vast swathes of the standard library and introducing new APIs, but almost never truly removing anything.

3 Likes

Yeah, but an imperfection != a bug. Although the issues I mentioned in the first post impact performance and annoy implementers, I don't feel that it's enough to justify the introduction of 'Codable2'. Moreover, in Codable there are more imperfect aspects of the API that cannot be addressed now.

Without commenting on the specific case at hand, yes, ABI stability means we are stuck with many of our choices into the indefinite future. Historically with ObjC the only opportunity to introduce ABI breaks was when porting to a new CPU architecture, since of course there's no existing software to be compatible with in that case (e.g. x86_64 uses C++ compatible exceptions in ObjC, but 32 bit x86 could not).

One of the reasons ABI stability was such a massive project was that it required manually reviewing all (to the extent feasible anyway) ABI-exposed choices in the project to determine if they were ones we were willing to commit to.

6 Likes

I wonder what are advantages in the commitment to maintain ABI between major versions? I see that an alternative would be to keep the ABI within one major version and distribute runtimes for the last few major versions. For example 3: libswiftCore5, libswiftCore6, libswiftCore7. At the current pace that would cover a timespan of a decade, and allow a cleanup every 3-5 years.
The ABI Stability Manifesto:

ABI stability enables OS vendors to embed a Swift standard library and runtime that is compatible with applications built with older or newer versions of Swift. This would remove the need for apps to distribute their own copy of these libraries on those platforms. It also allows for better decoupling of tools and better integration into the OS.

I totally agree on this, but it still could be covered by several bundled runtimes.

The difficulty there is that, as OS libraries incorporate Swift more and more, this also requires having extra copies of every OS library. Additionally it creates challenges for things like plugins where one binary is loaded into another.

4 Likes

It doesn't even take dynamically-loaded plugins; merely having prebuilt dynamic libraries (xcframeworks, "Build Libraries for Distribution") is enough to require committing to a stable ABI.

2 Likes

You mean the OS's size would be inflated?

Isn't a framework's author a decision-maker to support or not the framework on a particular version of the runtime? It used to be kinda ok in the days of Swift 2, 3, 4, 5.

1 Like

The size of the OS on disk, but also the memory used if apps using different versions of the libraries are running simultaneously. You could observe this effect in action on x86_64 Macs for a while: the first 32 bit app you started would have unexpectedly high memory costs as it brought a bunch of pages from the 32 bit framework stack in with it.

I don't mean to be discouraging, but we're relatively unlikely to find a holistic solution to this issue in this discussion. Many of the possibilities require large changes outside the scope of the Swift open source project, and most of them have been well-explored over the years. Lacking some radically new idea, I'm personally more interested in whether there are smaller subsets of the problem that can be tackled to address the needs of specific proposals.

5 Likes

Thanks for clarification. Good point. The disk space isn't a very valuable resource IMO, but the RAM definitely is.

Just to clarify, I'm not looking for a solution and I'm not trying to persuade anyone to change the plans. I was simply a bit surprised that the ABI will be maintained between versions, and a bit upset that some issues (mentioned at the begging in particular) will not have a chance to be fixed.

1 Like

You'd have to provide a different build of your framework for each runtime, since that's ultimately the app's decision. But you're right that there are ways around the problem, though the reference to Swift 2-3-4 isn't really correct: in that timeframe framework authors had to provide a different build of the framework for each compiler version, which is even more onerous.

Yep and I think it's in some sense already a working practice. Authors have to provide a different build for each cpu, each platform, in some cases each configuration (debug/release). A runtime version could be just another dimension in this matrix.

1 Like

Perhaps another option is for Swift to technically support multiple different ABI versions, but really only have two: Swift 5 and "nightly". Some projects, or even whole domains like embedded, don't necessarily care about a stable ABI.

That way in principle the Swift language and libraries could evolve unhindered.

Of course, there'd still be a significant subset of the same challenges, such as maintaining multiple implementations independently, documentation confusion (although half of StackOverflow is still written for Swift 2 & 3 already), etc.

It could be an interesting way to "pre-seed" the next major event that permits an ABI change in Apple's products, whether that be a new CPU architecture or whatever. My vague memory from the PPC -> Intel transition is that there was a bit of a mad dash by many teams at Apple to take advantage of the opportunity (since the transition didn't have much warning, even within Apple), and I'm not sure if all the things that could have been fixed made it in time.

1 Like