Dear Erica, Why would you create an enum with no cases?
This is such a great question! No-case enumerations represent a fairly obscure Swift “power tool”, and one that most developers are unaware of.
The answer, in a nutshell, is that they enable programmers to establish a mini-namespace with little overhead and an assurance that you can’t accidentally create type instances. Under their umbrella, you can group static members into namespaces, build singletons, create cohesive collections of functionality with a minimum surface eliminating freestanding functions, and in one outlier case provide services built around generic types.
Namespacing
For example, the standard library uses no-case enums to represent command line arguments for the current process. The Process
enum has no cases. It provides a native Swift interface to the command line argc (count)/argv (strings) arguments passed to the current process.
/// Command-line arguments for the current process. public enum Process { /// Access to the raw argc value from C. public static var argc: CInt { get } /// Access to the raw argv value from C. Accessing the argument vector /// through this pointer is unsafe. public static var unsafeArgv: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?> { get } /// Access to the swift arguments, also use lazy initialization of static /// properties to safely initialize the swift arguments. /// /// NOTE: we can not use static lazy let initializer as they can be moved /// around by the optimizer which will break the data dependence on argc /// and argv. public static var arguments: [String] { get } }
Singletons
If you’re thinking the preceding enumeration feels very much like a singleton, you would not be wrong. Consumers are prevented from creating instances and there’s a single entry point for all the functionality. Should you need supporting state, you can always include an embedded type within a no-case enumeration.
Here’s a real world example I built that offers synchronous text-to-speech generation :
/// Synchronous Text-to-Speech support public enum Speaker { /// Internal synthesizer private struct Synthesizer { static let shared = SynchronousSpeech() } /// Say the utterance passed as the argument public static func say(_ utterance: String) { Synthesizer.shared.say(utterance) } }
The synthesizer is hidden from view outside the module, as you see in the public declaration.
import AVFoundation import Foundation /// Synchronous Text-to-Speech support public enum Speaker { /// Say the utterance passed as the argument public static func say(_ utterance: String) }
Users cannot create instances and the single entry point (Speaker.say()
) limits access to the singleton’s restricted functionality.
Wrappers
I created a PlaygroundState
enumeration singleton to centralize and simplify playground code I access over and over. Instead of typing PlaygroundSupport.PlaygroundPage.current.needsIndefiniteExecution
, I use my PlaygroundState singleton to runForever()
.
The following example, which is cut down massively from its actual implementation, wraps several technologies including PlaygroundSupport and ProcessInfo to generalize access to simulator details and execution control:
/// Controls and informs the playground's state public enum PlaygroundState { /// Establishes that the playground page needs to execute indefinitely static func runForever() { page.needsIndefiniteExecution = true } /// Instructs Xcode that the playground page has finished execution. static func stop() { page.current.finishExecution() } /// The playground's environmental variables public static var processEnvironment: [String: String] { return processInfo.environment } #if !os(OSX) /// Simulator's device family public static var deviceFamily: String { return processEnvironment["IPHONE_SIMULATOR_DEVICE"] ?? "Unknown Device Family" } /// Simulator's device name public static var deviceName: String { return processEnvironment["SIMULATOR_DEVICE_NAME"] ?? "Unknown Device Name" } /// Simulator's firmware version public static var runtimeVersion: String { return processEnvironment["SIMULATOR_RUNTIME_VERSION"] ?? "Unknown Runtime Version" } #endif }
Consolidating Type Information through Generics
When reviewing SE-0101, Brent Royal-Gordon asked why the proposed MemoryLayout
type needed to be a struct:
I think grouping these into a type is a sensible approach, but I don’t like that it allows the creation of meaningless MemoryLayout instances. The simplest fix would be to make `MemoryLayout` an empty enum instead of an empty struct. This would convey that no MemoryLayout instances do or can exist.
This is a pretty outlier use of caseless enumerations but it’s a valuable one. Rewriting my original struct into an enum, produces the following example. It uses generics to extract information about types:
/// Accesses the memory layout of `T` through its /// `size`, `stride`, and `alignment` properties public enum MemoryLayout<T> { /// Returns the contiguous memory footprint of `T`. /// /// Does not include any dynamically-allocated or "remote" /// storage. In particular, `MemoryLayout.size`, when /// `T` is a class type, is the same regardless of how many /// stored properties `T` has. public static var size: Int { return sizeof(T.self) } /// For instances of `T` in an `Array`, returns the number of /// bytes from the start of one instance to the start of the /// next. This is the same as the number of bytes moved when an /// `UnsafePointer` is incremented. `T` may have a lower minimal /// alignment that trades runtime performance for space /// efficiency. The result is always positive. public static var stride: Int { return strideof(T.self) } /// Returns the default memory alignment of `T`. public static var alignment: Int { return alignof(T.self) } }
By using a caseless enumeration, users can query the types without ever creating instances, for example: MemoryLayout<Double>.size
, MemoryLayout<NSObject>.stride
, etc.
Like my posts? Consider buying a book. Content Update #1 is live. I also have books on Playgrounds (updated with the iOS 10 Swift Playgrounds App) and Structured Documentation for sale at iBooks and LeanPub.
Update:
See also: Natasha’s post on no-case enums and:
@ericasadun They also work for closing off code paths, as in https://t.co/3bhUBBQ2qq
— Joe Groff (@jckarter) July 18, 2016
4 Comments
Worth to mention Natashas post too https://www.natashatherobot.com/swift-enum-no-cases/
Added!
[…] Dear Erica: No-case Enums? […]
I like the creativity of using enumerations this way (namespacing, with little baggage), but I question whether there should just be a less “accidental” (for lack of a better word) way of doing this in the language (maybe “namespace class Xyz”?) Primarily just because of the definition of an enumeration:
“An enumeration defines a common type for a group of related values and enables you to work with those values in a type-safe way within your code… yada, yada”
With that said, in Java (I know Java enums are not the same as Swift enums), a great way to define a singleton is using enum:
enum Singleton {
INSTANCE;
…
}