% swift-enums-pattern-matching.tex — raw vs associated values, indirect,
% CaseIterable, exhaustive switch, @unknown default, @frozen + library
% evolution, the eight pattern kinds, if/guard/for case, enums as state machines.
% Source: docs/memos/swift-enums-pattern-matching.md.
% NOT from the memo (added from knowledge): the pattern-kind table (TSPL),
% SE numbers (0192 @unknown, 0260 @frozen, 0266 Comparable, 0295 Codable,
% 0380 if/switch expressions), NS_ENUM vs NS_CLOSED_ENUM, enum sizes,
% fallthrough cannot enter a binding case, multi-pattern binding rule,
% payload-less enums are Equatable/Hashable without declaring it.
% Deliberately NOT on paper (unsure): whether a missing @unknown default is an
% error in Swift 6 mode; implicit raw values for Double raw types.
% Build ONLY with: tools/print/print-sheet.py <this>.tex --dry-run
% @source: hiot monorepo, docs/school/sheets/swift/swift-enums-pattern-matching.tex — the SOURCE OF TRUTH; a copy anywhere else (e.g. artur.gurgul.pro) is regenerated from it, never edited
% @labels: area=swift kind=concept level=core platform=apple new=no round=missing-2026-09-25 topic=language,patterns
% @tags: associated-values, raw-values, indirect, caseiterable, exhaustive-switch, unknown-default, frozen, library-evolution, pattern-matching, if-case, state-machine
\documentclass[8pt]{extarticle}
\usepackage{printup-sheet}
\usepackage{array}

\lstdefinelanguage{SwiftSheet}{
  morekeywords={protocol,class,final,struct,enum,func,var,let,init,case,indirect,
    if,else,return,guard,self,nil,try,throws,private,some,mutating,extension,
    switch,default,where,in,for,true,false,break,is,as,static},
  sensitive=true, morecomment=[l]{//}, morecomment=[s]{/*}{*/}, morestring=[b]",
  literate={??}{{\hbox{?}\hbox{?}}}2 {==}{{\hbox{=}\hbox{=}}}2
           {!=}{{\hbox{!}\hbox{=}}}2 {->}{{\hbox{-}\hbox{>}}}2}

\newcommand\EQ{\hbox{=}\hbox{=}}
\newcommand\TL{\hbox{\textasciitilde}\hbox{=}}
\newcommand\arr{\hbox{-}\hbox{>}}
\newcolumntype{L}[1]{>{\raggedright\arraybackslash}p{#1}}

\tikzset{
  sb/.style={box, font=\ttfamily\scriptsize, inner sep=1.5pt, minimum height=5mm},
  st/.style={sb, rounded corners=5pt, minimum width=12mm},
  heap/.style={sb, draw=sheetOrange, fill=sheetOrange!10},
  bad/.style={sb, draw=sheetRed, fill=sheetRed!7},
  good/.style={sb, draw=sheetGreen, fill=sheetGreen!10},
  lbl/.style={font=\tiny, text=black!75, inner sep=1pt, align=center},
  hd/.style={font=\bfseries\small, anchor=west},
  ev/.style={font=\ttfamily\tiny, text=sheetBlue, inner sep=1pt},
}

\begin{document}

\sheettitle{Enums \& pattern matching — sum types + the 8 patterns}{swift · memo}

\oneliner{An enum value is \textbf{exactly one} of its cases (a \emph{sum type} / tagged union);
\textbf{raw values} are one compile-time constant per case, \textbf{associated values} are per-instance
payloads. \texttt{switch} must be \textbf{exhaustive}, patterns bind and filter, and for
library enums \texttt{@frozen} vs \texttt{@unknown default} decides who may add a case.}

\vspace{3pt}
\noindent\begin{tikzpicture}[sheet]
  \foreach \x in {5.6,11.0} \draw[sheetGrey!40] (\x,1.45) -- (\x,-1.85);
  % ── A: state machine ──
  \node[hd] at (0,1.3) {\textcolor{sheetBlue}{A} enum = state machine (code below)};
  \node[st, minimum width=9mm] (i) at (0.45,0.3) {idle};
  \node[st] (l) at (2.45,0.3) {loading};
  \node[good, rounded corners=5pt] (d) at (4.55,0.95) {loaded([Item])};
  \node[bad, rounded corners=5pt] (f) at (4.55,-0.35) {failed(String)};
  \draw[flow] (i) -- node[ev, above]{.fetch} (l);
  \draw[flow] (l.north east) -- node[ev, above left, pos=0.45]{.success} (d.west);
  \draw[flow] (l.east) -- node[ev, above right, pos=0.35]{.failure} (f.west);
  \draw[hot] (f.south west) to[bend left=25] node[ev, below]{.retry} (l.south);
  \node[lbl, anchor=west] at (0,-1.45) {one case at a time: no \texttt{isLoading \&\& error \hbox{!}\hbox{=} nil}\\
    impossible combos; a payload exists only in its state};
  % ── B: indirect ──
  \node[hd] at (5.7,1.3) {\textcolor{sheetBlue}{B} \texttt{indirect}: payload in a heap box};
  \node[sb] (r) at (8.3,0.75) {.add(\_, \_)};
  \node[heap] (b1) at (6.9,-0.05) {box: .value(1)};
  \node[heap] (b2) at (9.7,-0.05) {box: .mul(\_, \_)};
  \node[heap] (b3) at (8.7,-0.85) {box: .value(2)};
  \node[heap] (b4) at (10.35,-0.85) {.value(3)};
  \draw[flow] (r) -- (b1); \draw[flow] (r) -- (b2);
  \draw[flow] (b2) -- (b3); \draw[flow] (b2) -- (b4);
  \node[lbl, anchor=west] at (5.7,-1.55) {without it: infinite size $\to$ compile error.\\boxes are refcounted + immutable: value semantics kept};
  % ── C: library evolution ──
  \node[hd] at (11.1,1.3) {\textcolor{sheetBlue}{C} a framework adds a case};
  \node[sb, anchor=west] (v1) at (11.1,0.75) {SDK v1: .wifi .cell};
  \node[sb, anchor=west, draw=sheetOrange, fill=sheetOrange!10] (v2) at (11.1,0.15) {SDK v2: + .satellite};
  \node[bad, anchor=west] (c1) at (11.1,-0.6) {default:};
  \node[good, anchor=west] (c2) at (13.1,-0.6) {@unknown default:};
  \node[lbl, text=sheetRed] at (11.9,-1.25) {new case swallowed\\silently};
  \node[lbl, text=sheetGreen!60!black] at (14.1,-1.25) {runs at runtime AND\\warns when you rebuild};
  \node[lbl, anchor=west] at (14.25,0.45) {non-\texttt{@frozen}\\= may grow};
\end{tikzpicture}

\begin{multicols}{2}

\section{How it works}
\begin{itemize}
  \item \textbf{Raw values} \texttt{enum S: Int \{ case ok = 200 \}}: one literal type, fixed at
        compile time, gives \texttt{RawRepresentable} + failable \texttt{init?(rawValue:)}.
        Implicit: \texttt{Int} counts up from the previous value (start 0);
        \texttt{String} = the case name. \textbf{Raw XOR associated} — never both.
  \item \textbf{Associated values} \texttt{case user(id: Int, name: String)}: payload per
        instance, different types per case. Size = largest payload + tag (spare bits
        reused); payload-less: 1 byte up to 256 cases, a single case: 0 bytes.
  \item \textbf{\texttt{indirect}} on the enum or one case: payload boxed on the heap $\to$
        recursive types (trees, linked lists, ASTs).
  \item \textbf{Synthesis:} payload-less enums are Equatable + Hashable \emph{without
        declaring it}. With payloads: declare it, all payloads must conform.
        \texttt{Comparable} by declaration order (Swift 5.3, SE-0266; not for raw-value
        enums); \texttt{Codable} with payloads (5.5, SE-0295);
        \texttt{CaseIterable} $\to$ \texttt{allCases} only without payloads.
  \item No stored instance properties (\texttt{static} ones are fine); methods,
        computed props, \texttt{mutating func} that assigns \texttt{self = .x}. A caseless
        \texttt{enum API \{\}} is a namespace that cannot be instantiated.
  \item \textbf{Exhaustive \texttt{switch}}: no implicit fallthrough; \texttt{fallthrough}
        skips the next pattern check and cannot enter a case that binds variables.
        Swift 5.9: \texttt{let x = switch s \{ \ldots \}} (SE-0380).
  \item \textbf{Library evolution} (\texttt{BUILD\_LIBRARY\_FOR\_DISTRIBUTION}):
        enums are \emph{non-frozen} by default — clients need \texttt{@unknown default}
        (SE-0192, Swift 5). \texttt{@frozen} (SE-0260) promises no new case ever: faster
        layout, no \texttt{@unknown} needed, adding a case breaks ABI. ObjC:
        \texttt{NS\_ENUM} imports non-frozen, \texttt{NS\_CLOSED\_ENUM} frozen. Without
        library evolution every enum is effectively frozen: a new case breaks
        clients' switches at \textbf{compile} time.
\end{itemize}

\section{Example — a state machine}
\begin{lstlisting}[language=SwiftSheet]
enum Load { case idle, loading, loaded([Item]), failed(String) }
enum Event { case fetch, retry, success([Item]), failure(String) }
extension Load {
  mutating func send(_ e: Event) {
    switch (self, e) {                       // tuple pattern
    case (.idle, .fetch), (.failed, .retry): self = .loading
    case (.loading, .success(let items)):    self = .loaded(items)
    case (.loading, .failure(let m)) where !m.isEmpty:
                                             self = .failed(m)
    default: break                           // illegal: ignore
    }
  }
}
if case .loaded(let items) = state { show(items) }
for case .failed(let m) in history { log(m) }  // filter + bind
\end{lstlisting}

\columnbreak

\section{The 8 pattern kinds (TSPL)}
{\footnotesize
\begin{tabular}{@{}L{17mm}L{30mm}L{27mm}@{}}
\toprule
\textbf{Pattern} & \textbf{Looks like} & \textbf{Note} \\
\midrule
wildcard & \texttt{\_} & matches, ignores \\
identifier & \texttt{x} (in \texttt{let x = \ldots}) & binds anything \\
value-binding & \texttt{let (x, y)}, \texttt{.a(let x)} & \texttt{let} distributes inward \\
tuple & \texttt{(200, \_)}, \texttt{(let c, true)} & element-wise \\
enum case & \texttt{.failed(let m)}, \texttt{.some(x)} & works on Optional too \\
optional & \texttt{let x?} & sugar for \texttt{.some(x)} \\
type-casting & \texttt{is Int}, \texttt{let s as String} & check / check+cast \\
expression & \texttt{400..<500}, \texttt{"GET"} & calls \texttt{\TL} \\
\bottomrule
\end{tabular}}

\begin{itemize}
  \item \textbf{\texttt{\TL}}: default for Equatable is \texttt{\EQ}; ranges use
        \texttt{contains}. Overload \texttt{static func \TL(pattern: P, value: V) \arr{} Bool}
        to match your own (e.g. a predicate, a regex).
  \item \texttt{where} is a \textbf{guard on the case}, not a pattern: runs after binding.
  \item \texttt{case .a(let x), .b(let x):} — every alternative must bind the
        \textbf{same names with the same types}.
  \item Where patterns live: \texttt{switch}, \texttt{if/guard/while case},
        \texttt{for case \ldots\ in}, \texttt{catch}, and every \texttt{let}.
\end{itemize}

\section{Interview traps}
\begin{itemize}
  \trap{\texttt{default:} on an SDK enum hides new cases; \texttt{@unknown default}
        still warns about the ones you did not list.}
  \trap{Persisted raw values: renaming a case (String raw = name!) $\to$
        \texttt{init?(rawValue:)} gives \texttt{nil}. Pin raw values.}
  \trap{Equatable on a payload enum is not free: one non-Equatable payload and
        synthesis fails.}
  \trap{Adding a case to a public enum is a breaking change either way —
        compile-time (source libs) or runtime-path (\texttt{@unknown}).}
  \trap{\texttt{state \EQ{} .loading} needs \texttt{Equatable} (payloads too);
        \texttt{if case .loading = state} needs nothing.}\end{itemize}

\section{Likely questions}
\begin{enumerate}
  \item Raw and associated at once? — no; mutually exclusive.
  \item Why \texttt{indirect}? — a recursive payload needs a box.
  \item \texttt{@unknown default} vs \texttt{default}? — future cases + keeps the warning.
  \item Why enums over flags/optionals for state? — illegal states unrepresentable.
  \item How does \texttt{case 1...5} match? — expression pattern via \texttt{\TL}.
  \item Payload without a switch? — \texttt{if case let}, or a computed \texttt{var data: Data?}.
\end{enumerate}

\section{Remember}
\textbf{``Raw = a label, associated = a payload; switch covers all; frozen = closed forever.''}

\end{multicols}

\noindent{\footnotesize\color{sheetGrey}\textit{Related:} swift-optionals · functional-design
(ADTs) · unidirectional-state-machines · error-handling (\texttt{catch} patterns) ·
swift-equatable-hashable-comparable · ownership-and-memory-layout}

\end{document}
