Dear Erica: No-case Enums?

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:

4 Comments

  • Worth to mention Natashas post too https://www.natashatherobot.com/swift-enum-no-cases/

  • […] 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;

    }

Join the Discussion

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>