[Second Review] SE-0487: Extensible Enums

Hi Swift community,

The second review of SE-0487: Extensible Enums begins now and runs through July 10th, 2025.

After discussing feedback from the first review, the Language Steering Group decided that:

  • The naming of the new property could be more descriptive of its purpose. The best option from the original proposal seems to be @nonexhaustive.
  • the modifying enum indicating the staging in would be better as a modifier of the main attribute i.e. @nonexhaustive(warn).

The proposal authors have modified the proposal accordingly. A diff of the changes can be found here.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager. When emailing the review manager directly, please keep the proposal link at the top of the message.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

https://github.com/apple/swift-evolution/blob/main/process.md

Thank you,

Ben Cohen
Review Manager

13 Likes

+1, this is a highly appreciated, very important and probably long-overdue change that I fully support.

I've lost count on how many sad faces I've seen over the years when somebody wanted to add a case to their public enum MyError and had to learn that in non-resilient modules that's API breaking... And of course, errors are just one very unfortunate manifestation of this. The workarounds that we have today are awkward, require a lot of typing and don't have the fidelity of enums.

6 Likes

I'm not sure I understand why it's a problem to introduce source breaking changes/increase the major semver?

If the library is not resilient then doesn't it imply that the consumer of the library will be recompiling their project when updating the library? So they will then be able to update the switch statements in their source code as needed?

I've only ever made iOS apps with Swift, but I'm not sure what scenario the dependencies of a project change without the rebuilding of the consuming project, apart from system libraries which are resilient anyway. Is there something like server side swift where non resilient libraries change without recompiling the project?

The problem is not only relevant for Swift Server use-cases but any use-case that uses Swift packages for dependencies. The Swift Package Manager uses Semantic Versioning when resolving dependencies to find the right set of versions across the dependency graph. Semantic Versioning is based on the public API surface of a package. Additive changes to the public API are expected to result in a new minor version. Breaking changes to the public API are expected to result in a new major version.

While from the perspective of a single package introducing a new major version might not seem problematic when looking at the larger ecosystem major versions are of a tremendous impact. Each major versions requires every upstream package that depends on it to release a new version (often a new major) to support the new release. Lower packages in the ecosystem such as swift-collections or swift-nio often want to avoid new majors as much as possible to avoid impacting the rest of the ecosystem.

Now coming back to this proposal. Public enums in packages as they are behaving today don't allow adding new cases without forcing the package to release a new major since consumers can only exhaustively match over them. This makes enums in public APIs in source stable modules such as packages a huge foot-gun and it is often recommended to avoid using enums in public APIs for that reason. This is quite sad since enums are a powerful language feature. This proposal aims to enable using them for public APIs while allowing developers to add new cases in future minor releases.

6 Likes

Can't the upstream package just choose not to make a new release? And if the upstream package only handles the enum as an implementation detail, it shouldn't need to bump the major version at all when it does. I really don't see the big issue here.

Maybe I'm reading the proposal wrong, and a dependency making a version bump means your package must also make a version bump even if its own external API doesn't change, but if that's the case, then it seems like an issue with SwiftPM and not the language.

I disagree with the general idea that client code can or should be able to gracefully handle unknown cases, and that the potential for product bugs for not being required to handle new cases is preferable in the ecosystem (outside of Apple's in-the-OS releases) to just making a new release.

It depends on how you’ve declared your package manifest. NodeJS works in a similar way.

If you declare that your package is compatible with versions 1.1.0..<2.0.0 of a dependency, you don’t have to update when 1.2.0 releases, but you will when 2.0.0 releases. On the other hand, declaring that you’re compatible with 2.0.0 before it’s released is dangerous, since you don’t know what breaking changes there will be.

3 Likes

The problem is that not all packages will do so equally quickly. Imagine an ecosystem in which we have SwiftNIO at version 2, package A (which depends on version 2 of NIO) and package B (which depends on version 2 of NIO) as libraries, and then application C.

In the future, the NIO team release NIO version 3. Initially, nothing changes as A and B have not released new versions. The result is that application C continues to resolve NIO version 2.

At some point in the future, B updates to support NIO 3, and drops its support for NIO 2. This is fine. However, application C cannot resolve the new version of B because it would introduce a conflict: A requires NIO 2, B requires NIO 3, that can't work. So C does not resolve the new version of B, and stays on NIO 2.

Only when A also moves to NIO 3 can C start to move forward again.

Now, if you multiply this out to the scale of core packages like swift-nio and swift-collections you can start to see the problem. The server ecosystem frequently encounters dependency trees with hundreds of explicitly specified dependencies on these packages. Until all of them move, you are stuck on the older versions. This forces either the entire library ecosystem to continue to support their older versions (backporting bugfixes), or tell application authors that they won't get bugfixes until their dependencies support the new base library, which application authors do not control.

3 Likes

Can they statically bundle their specific version of the library dep in these cases?

No: SwiftPM compiles the whole package tree statically, so each library may appear only once.

is it possible that a fix is in SwiftPM then and not the language?

SwiftPM can minimise the cost of the problem, but ultimately I think it's a mistake for us to make the library-evolution mode too different from the source-available mode in terms of supported language features. Being able to choose not to issue compiler errors when we add cases is a feature, not a bug.

3 Likes

To add to what @lukasa said: I personally would like to see Swift PM supporting multiple different majors of a single package inside a single dependency graph. However, even if we get this feature in the future, I think that a single new case in a single enum should still not require a new major. Having two different majors of a single dependency has broad impact on things like the build time and the binary size.

3 Likes

A little addition from my side on the topic of "multiple versions/majors" of the same package: Imho it is easy to overestimate how much adding build support for this actually "helps" IRL.

I am not saying it does not enable certain use cases, or that it is not worth pursuing, but the consequences of having multiple versions of the same thing in a process are not fun to manage in my experience. Especially when dealing with more prominent components (like swift-nio) things become VERY unwieldy quickly (and a proper nightmare to debug if things go brrr - ask me how I know ; )) I'd suggest avoiding this as long as humanly possible.

To me, the idea of “just fix the SwiftPM build” seriously downplays the underlying complexities.

5 Likes