[swift-evolution] [Review] SE 0192 - Non-Exhaustive Enums
nevin.brackettrozinsky at gmail.com
Thu Dec 21 14:16:30 CST 2017
Making enums non-exhaustive solves a problem for authors of precompiled
frameworks which apps can dynamically link against. These libraries need to
preserve binary compatibility, in the sense that apps should continue to
work with newer versions of the library.
Now, that creates a problem for app developers, wherein the compiler no
longer informs them when their switch statements don’t cover all known enum
cases. This can be solved by introducing “future” for switch statements,
which acts like “default” but will warn if it is provably reachable.
Perhaps the most common scenario, however, is developers who write
multi-module apps. Such apps could include modules with the same author,
modules included as source, and even precompiled binary modules. As long as
these are all bundled with the app, they cannot possibly change out from
under it at runtime, so binary-compatibility is not a concern.
I think it is important that any solution to the problems of dynamically
linked libraries should not adversely impact authors of multi-module apps.
It sounds like John McCall has an approach to handle this with version
locking of dependencies.
Another possibility is to say that public enums are non-exhaustive by
default if the module is built with resilience enabled, but if resilience
is not enabled then enums are exhaustive.
Alternatively, we could introduce a concept of “the entire thing I am
building, including all its statically-linked modules”, and let enums be
exhaustive within that domain while still defaulting to non-exhaustive
I am glad to see vigorous debate and brainstorming on this thread, and I am
confident that we will find a good solution.
On Thu, Dec 21, 2017 at 2:26 PM, John McCall via swift-evolution <
swift-evolution at swift.org> wrote:
> On Dec 21, 2017, at 2:03 PM, Jordan Rose via swift-evolution <
> swift-evolution at swift.org> wrote:
> On Dec 20, 2017, at 12:35, Karl Wagner <razielim at gmail.com> wrote:
> On 20. Dec 2017, at 19:54, Jordan Rose <jordan_rose at apple.com> wrote:
> On Dec 20, 2017, at 05:36, Karl Wagner via swift-evolution <
> swift-evolution at swift.org> wrote:
> On 19. Dec 2017, at 23:58, Ted Kremenek via swift-evolution <
> swift-evolution at swift.org> wrote:
> The review of "SE 0192 - Non-Exhaustive Enums" begins now and runs through *January
> 3, 2018*.
> The proposal is available here:
> +1, it needs to happen (and ASAP, since it _will_ introduce
> source-breaking changes one way or the other).
> I think non-exhaustive is the correct default. However, does this not mean
> that, by default, enums will be boxed because the receiver doesn’t know
> their potential size?
> It's not always boxing, but yes, there will be more indirection if the
> *compiler* can't see the contents of the enum. (More on that below.)
> That would mean that the best transition path for multi-module Apps would
> be to make your enums @exhaustive, rather than adding “default” statements
> (which is unfortunate, because I imagine when this change hits, the way
> you’ll notice will be complaints about missing “default” statements).
> Yep, that's going to be the recommendation. The current minimal-for-review
> implementation does not do this but I'd like to figure out how to improve
> that; at the very least it might be a sensible thing to do in the migrator.
> I do have some thoughts about how we could ease the transition (for this
> and other resilience-related changes), but it’s best to leave that to a
> separate discussion.
> The one thing I’m still not overly fond of is the name - I would like us
> to keep the set of resilience/optimisation related keywords to a minimum.
> “exhaustive” for enums feels an awful lot like “fixed_contents” for structs
> - couldn’t we come up with a single name which could be used for both? I
> don’t think anybody’s going to want to use “exhaustive” for structs.
> The core team was very focused on this too, but I contend that
> "exhaustive" is not about optimization and really isn't even about
> "resilience" (i.e. the ability to evolve a library's API while preserving
> binary compatibility). It's a semantic feature of an enum, much like 'open'
> or 'final' is for classes, and it affects what a client can or can't do
> with an enum. For libaries compiled from source, it won't affect
> performance at all—the *compiler* still knows the full set of cases in the
> *current* version of the library even if the programmer is forced to
> consider future versions.
> I'm working on the fixed-contents proposal now, though it won't be ready
> for a while, and the same thing applies there: for structs compiled from
> source, the compiler can still do all the same optimizations. It's only
> when the library has binary compatibility concerns that we need to use
> extra indirection, and then "fixed-contents" becomes important. (As
> currently designed, it doesn't affect what clients can do with the struct
> at all.) This means that I don't expect a "normal" package author to write
> "fixed-contents" at all (however it ends up being spelled), whereas
> "exhaustive" is a fairly normal thing to consider whenever you make an enum
> I hope that convinces you that "fixed-contents" and "exhaustive" don't
> need to have the same name. I don't think anyone loves the *particular* name
> "exhaustive", but as you see in the "Alternatives considered" we didn't
> manage to come up with anything significantly better. If reviewers all
> prefer something else we'd consider changing it.
> Thanks for responding!
> When you say “libraries compiled from source”, what do you mean?
> - Other targets in your project
> - Source packages built through SwiftPM / CocoaPods / Carthage / other
> And I was being imprecise with the terminology, but also
> - Libraries built by someone else but designed to be embedded into an app,
> so that there's no chance of a different version showing up at run-time.
> As for whether its a resilience feature: actually it is completely a
> resilience feature. The effects on switching are only side-effects; really
> what “exhaustive” or “nonexhaustive” are saying is literally that cases may
> be added later. Even if we added private cases, you wouldn’t need to mark
> those enums as specially exhaustive or not; that would be implied. It’s an
> accommodation for things which don’t exist yet, so really, it is all about
> resilience IMO.
> "Resilience", as an admittedly fuzzily-defined term in the Swift project, specifically
> refers to what changes can be made without breaking binary compatibility
> <https://github.com/apple/swift/blob/master/docs/Lexicon.rst>. It does
> not refer to *every* change you can make to a library. (For comparison,
> adding a field to a struct is not source-breaking in Swift. We would like
> to make it not ABI-breaking either; that proposal's coming soon.)
> Anyway, as I see it, library authors in general ought to be happy about
> + Their libraries become safer by default, so they can make changes in the
> future without having to worry about breakage
> + It doesn’t affect your code inside of a module, so it only affects types
> they already explicitly marked “public”
> That's the intent.
> The only people who lose are multi-module App developers, because they are
> “library authors” who don’t need to care about evolution, and now need to
> add attributes to things they wouldn’t have to before, or suffer language
> and performance penalties. Their libraries become less reusable and not
> For example, I have an App for which I wrote a cross-platform model
> framework in Swift. When I compile it as a framework inside my App, it is
> bundled there forever. However, I use the same code to build libraries for
> Linux, which I would like to ship in binary form to 3rd-parties. Am I
> supposed to litter my code with annotations to mark those types as final,
> just to make the App fast and convenient to code? What happens when I need
> to fix a bug and distribute an updated copy, this means the 3rd-parties
> need to recompile (which they won’t do…).
> Typically, for such a problem, I would recommend using a static library
> instead. But we don’t have those, and anyway they’re not always the best
> thing these days. So that’s why I started a new thread about creating a
> “@static” import, so App developers can go back to all the conveniences
> they had before.
> There won't be a perf penalty, but yes, I do expect multi-module apps to
> use 'exhaustive' on most of their enums, because they don't need the
> futureproofing. Maybe this should have been mentioned more explicitly in
> the proposal.
> As a perhaps more long-term design note, I think modules ought to have the
> ability to version-lock themselves to one or more of their dependencies.
> They would still be required to obey access control as if they were outside
> those dependencies, but we would suppress some of the *semantic*
> consequences of being outside the module, such as the need to assume
> non-exhaustiveness by default.
> That is, there would be two independent axes of library dependency: source
> vs. binary and version-compatible vs. version-locked:
> - a source dependency allows the compiler to take advantage of the
> implementation of public entities when generating code
> - a version-locked dependency allows the compiler to take advantage of
> the implementation of public entities when enforcing semantics
> Apps would generally elect to primarily use version-locked source
> dependencies because they're just pulling down source libraries (e.g. from
> github) and are comfortable with updating their code if the library changes.
> Source libraries on github would generally want to use version-compatible
> source dependencies because version-locking would put their clients in
> "library hell" if the locking didn't all agree.
> Binary dependencies could reasonably use either.
> swift-evolution mailing list
> swift-evolution at swift.org
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the swift-evolution