Swift
Enum
Protocol
Swift Programming
iOS Development

How to require an enum be defined in Swift Protocol

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Swift protocols can require properties, methods, and associated types, but they cannot directly declare a nested enum that every conforming type must implement. The usual workaround is to require an associated type and then constrain it so it behaves the way your design needs.

Why a Protocol Cannot Declare a Concrete Enum

A protocol describes an interface, not stored implementation details. Because of that, Swift lets a protocol say "a conforming type must provide some type here," but it does not let the protocol define a nested enum body with fixed cases for every adopter.

That distinction matters. If your goal is "every conforming type must expose a state type," a protocol can express that. If your goal is "every conforming type must define exactly these enum cases," a protocol alone cannot enforce it.

Use an Associated Type for the Enum-Like Requirement

The cleanest approach is to require an associated type with constraints that match how you want to use the enum. In many designs, CaseIterable, Hashable, and RawRepresentable are enough.

swift
1protocol Workflow {
2    associatedtype Step: CaseIterable & Hashable & RawRepresentable
3        where Step.RawValue == String
4
5    static var initialStep: Step { get }
6    func handle(step: Step)
7}
8
9struct CheckoutWorkflow: Workflow {
10    enum Step: String, CaseIterable {
11        case cart
12        case payment
13        case confirmation
14    }
15
16    static let initialStep: Step = .cart
17
18    func handle(step: Step) {
19        switch step {
20        case .cart:
21            print("Show cart")
22        case .payment:
23            print("Collect payment")
24        case .confirmation:
25            print("Show receipt")
26        }
27    }
28}

This does not prove that Step is literally an enum, but it does express the behaviors that enum-based workflows usually need: finite cases, hashability, and raw string values.

Keep the Type Nested for Readability

Even though the protocol cannot force nesting, you can still adopt a convention where the associated type is declared as a nested enum inside the conforming type. That makes call sites clearer because the name stays attached to the owner.

swift
1let workflow = CheckoutWorkflow()
2for step in CheckoutWorkflow.Step.allCases {
3    workflow.handle(step: step)
4}

Using a nested enum also keeps related logic together. When another developer opens CheckoutWorkflow, they immediately see its valid states and how they are handled.

If You Need Shared Cases Across Types

Sometimes every conforming type really should use the same list of cases. In that situation, a protocol is not the right tool for defining the cases. Instead, create one shared enum and have the protocol refer to it directly.

swift
1enum SyncState: String, CaseIterable {
2    case idle
3    case running
4    case failed
5}
6
7protocol Syncable {
8    var state: SyncState { get set }
9    mutating func transition(to newState: SyncState)
10}

This design is more rigid, but it is also more explicit. If the cases must be identical, using one concrete enum is simpler than trying to model the same rule through protocol constraints.

What Swift Can and Cannot Enforce

Swift can enforce that a conforming type provides an associated type named Step. Swift can also enforce that the type conforms to protocols such as CaseIterable. What Swift cannot do is say "the associated type must be an enum declaration with exactly these cases." There is no language feature for that level of structural enforcement.

When you need exact cases plus polymorphism, choose one of two designs:

  • Use one shared enum for all conforming types
  • Use an associated type and accept that the protocol only constrains behavior, not syntax

Common Pitfalls

  • Declaring enum inside a protocol is not supported, so the compiler will reject that design immediately.
  • 'CaseIterable does not guarantee a type is an enum, only that it provides allCases.'
  • If exact cases matter, an associated type is too flexible and will not fully encode the rule.
  • Avoid over-constraining the associated type unless you truly need RawRepresentable or other protocol requirements.

Summary

  • A Swift protocol cannot directly require a nested enum definition.
  • Use an associated type to require an enum-like type from conforming types.
  • Add constraints such as CaseIterable and Hashable to express the behaviors you need.
  • If all adopters must share the same cases, define one concrete enum and reference it from the protocol.

Course illustration
Course illustration

All Rights Reserved.