Introduction

It is a truth universally acknowledged, that a programmer in possession of an enum with many cases, must eventually be in want of dynamic enumeration over them.

Enumeration types without associated values (henceforth referred to as "simple enums") have a finite, fixed number of cases, yet working with them programmatically is challenging. It would be natural to enumerate all the cases, count them, determine the highest rawValue, or produce a Collection of them. However, despite the fact that both the Swift compiler and the Swift runtime are aware of this information, there is no safe and sanctioned way for users to retrieve it. Users must resort to various workarounds in order to iterate over all cases of a simple enum.

We propose the introduction of a protocol, CaseIterable, to indicate that a type has a finite, enumerable set of values. Moreover, we propose an opt-in derived implementation of CaseIterable for the common case of a simple enum.

Motivation

Use cases

Examples online typically pose the question "How do I get all the enum cases?", or even "How do I get the count of enum cases?", without fully explaining what the code will do with that information. To guide our design, we focus on two categories of use cases:

The code must greedily iterate over all possible cases, carrying out some action for each case. For example, imagine enumerating all combinations of suit and rank to build a deck of playing cards:

To limit our scope, we are primarily interested in simple enums—those without any associated values—although we would also like to allow more complicated enums and structs to manually participate in this mechanism.

The second use case suggests that access by contiguous, zero-based integer index is important for at least some uses. At minimum, it should be easy to construct an Array from the list of cases, but ideally the list could be used like an array directly, at least for simple enums.

Workarounds

The most basic approach to producing a collection of all cases is by manual construction:

Or using a switch statement, making it a compilation error to forget to add a case, as with Dave Sweeris's Enum Enhancer (which includes some extra functionality to avoid the boilerplate required for .cases and .labels):

They are ad-hoc and can't benefit every enum type without duplicated code.

They are not standardized across codebases, nor provided automatically by libraries such as Foundation and {App,UI}Kit.

They are dangerous at worst, bug-prone in most cases (such as when enum cases are added, but the user forgets to update a hard-coded static collection), and awkward at best.

Resilience implications

This last point is especially important as we begin to concern ourselves with library resilience. Future versions of Swift should allow library authors to add and deprecate public enum cases without breaking binary compatibility. But if clients are manually constructing arrays of "all cases", those arrays will not correspond to the version of the library they are running against.

At the same time, the principle that libraries ought to control the promises they make should apply to any case-listing feature. Participation in a "list all cases" mechanism should be optional and opt-in.

Precedent in other languages

Rust does not seem to have a solution for this problem.

C#'s Enum has several methods available for reflection, including GetValues() and GetNames().

Java implicitly declares a static values() function, returning an array of enum values, and valueOf(String name) which takes a String and returns the enum value with the corresponding name (or throws an exception). More examples here.

The Template Haskell extension to Haskell provides a function reify which extracts info about types, including their constructors.

Proposed solution

We propose introducing a CaseIterable protocol to the Swift Standard Library. The compiler will derive an implementation automatically for simple enums when the conformance is specified.

Detailed design

The compiler will synthesize an implementation of CaseIterable for an enum type if and only if:

the enum contains only cases without associated values;

the enum declaration has an explicit CaseIterable conformance (and does not fulfill the protocol's requirements).

Enums imported from C/Obj-C headers will not participate in the derived CaseIterable conformance.

Cases marked unavailable will not be included in allCases.

The implementation will not be synthesized if the conformance is on an extension — it must be on the original enum declaration.

Source compatibility

This proposal only adds functionality, so existing code will not be affected. (The identifier CaseIterable doesn't make very many appearances in Google and GitHub searches.)

Effect on ABI stability

The proposed implementation adds a derived conformance that makes use of no special ABI or runtime features.

Effect on API resilience

User programs will come to rely on the CaseIterable protocol and its allCases and AllCases requirements. Due to the use of an associated type for the property's type, the derived implementation is free to change the concrete Collection it returns without breaking the API.

Alternatives considered

The functionality could also be provided entirely through the Mirror/reflection APIs. This would likely result in much more obscure and confusing usage patterns.

Provide a default collection

Declaring this in the Standard Library reduces the amount of compiler magic required
to implement the protocol. However, it also exposes a public unsafe entrypoint to the
reflection API that we consider unacceptable.

A type inherently represents a set of its possible values, and as such, for val in MyEnum.self { } could be a natural way to express iteration over all cases of a type. Specifically, if the metatype MyEnum.Type could conform to Collection or Sequence (with Element == MyEnum), this would allow for-loop enumeration, Array(MyEnum.self), and other use cases. In a generic context, the constraint T: CaseIterable could be expressed instead as T.Type: Collection.

Absent the ability for a metatype to actually conform to a protocol, the compiler could be taught to treat the special case of enum types as if they conformed to Collection, enabling this syntax before metatype conformance became a fully functional feature.