Parameter packs & the new syntax (5.9 → 6.4)

swift · memo

In one line: A parameter pack (5.9) lets one generic declaration take any number of type parameters: <each T> declares the pack, repeat pattern expands the pattern once per element, and each x names the current element — no more 1…10 overloads. Around it, every release since added small syntax that deletes boilerplate: expression if/switch, trailing commas, raw identifiers, weak let, module selectors, some P?, @diagnose, await in defer.

Download PDF Print view LaTeX source

How packs work

  • func f<each T>(_ v: repeat each T): T is a type pack, v a value pack; a call f(1, "a", true) binds T = {Int, String, Bool}. Constraints apply per element: <each T: Equatable>.
  • repeat P is a pack expansion: P must mention a pack with each; it becomes a comma list — repeat [each T] → [Int], [String], [Bool]. Values: (repeat each v) builds a tuple (SE-0399).
  • Same shape: two packs in one expansion (repeat (each L, each R)) must have the same length — the compiler infers and enforces it.
  • No count, no subscript: you cannot index a pack. You expand it, or (6.0, SE-0408) iterate it: for x in repeat each v, one element per iteration, lazily — break stops evaluating the rest. In 5.9: a local generic func + repeat try f(each v).
  • Variadic types (SE-0398, 5.9): struct Zip<each S: Sequence>; at most one pack per type; store it as a tuple (repeat each S).
  • Where you meet them: SwiftUI’s ViewBuilder (no 10-child limit), zip-style APIs over any arity, typed tuple helpers.

Picture — one pattern, stamped per element

Parameter packs & the new syntax (5.9 → 6.4) — figure 1

Example — packs

func tuplify<each T>(_ v: repeat each T) -> (repeat each T) {
  (repeat each v)                        // 5.9: tuple expansion
}
let t = tuplify(1, "a", true)            // (Int, String, Bool)

func allEqual<each E: Equatable>(_ l: (repeat each E),
                                 _ r: (repeat each E)) -> Bool {
  for (a, b) in repeat (each l, each r) { // 6.0 pack iteration
    guard a == b else { return false }    // rest never evaluated
  }
  return true
}
allEqual((1, "a"), (1, "b"))             // false; E = Int, String
struct Zip<each S: Sequence> { let seqs: (repeat each S) }

Example — the new syntax

let tint: Color = if ok { .green } else { .red }       // 5.9
let p = Point(x: 1, y: 2,)                             // 6.1
@Test func `rejects an empty email`() {}               // 6.2
enum Shade { case `50`, `100` }; let s = Shade.`50`    // 6.2
final class Tag: Sendable { weak let owner: Owner? }   // 6.3
let x = ModuleA::getValue()                            // 6.3
weak var delegate: any CellDelegate?                   // 6.4
@diagnose(DeprecatedDeclaration, as: warning,
          reason: "legacy bridge until 3.0")           // 6.4
func bridge() { oldAPI() }
func upload() async throws {
  let h = try await open()
  defer { await withTaskCancellationShield { await h.close() } }
  try await h.send(payload)                            // 6.4
}

(Owner: a Sendable class; CellDelegate: AnyObject-bound.)

FeatureSwiftSEThe rule an interviewer probes
if/switch expression5.90380only as return value, assignment, let/var init — not inside a larger expression; else mandatory; one expression per branch; branches type-checked independently (if c { 0 } else { 1.0 } fails — annotate the type); a throw/fatalError branch is fine
trailing commas6.10439in ( ), [ ], < > lists: args, params, tuples, subscripts, capture lists, generics, interpolation. Not in if/guard conditions, inheritance clauses, where, case a, b
raw identifiers6.20451any characters between backticks except backtick, backslash, newline; not all whitespace, not only operator characters; use sites need the backticks too
weak let6.30481still becomes nil when the object dies, but cannot be reassigned ⇒ a final class with it can be Sendable
module selectors M::x6.30491pick the module when two imports export the same name (also on members: s.Foundation::data(using:)); lookup starts at module top level, so locals/generic params are unreachable; never on a new declaration’s name
some P? / any P?6.40521= (some P)? / (any P)? — the optional wraps the opaque/existential, no parentheses needed
@diagnose6.40522@diagnose(Group, as: error|warning|ignored, reason: "…") on one declaration (func, type, extension, import…); order-sensitive; module flag -suppress-warnings still wins
await in defer6.40493only in an async context; awaited on every scope exit (a hidden suspension point); cancellation is not suppressed — wrap in withTaskCancellationShield (SE-0504)
access-level imports6.00409internal import X keeps X out of your public API (error if a public signature uses it); a bare import stays public in modes 5 and 6 unless InternalImportsByDefault
package access5.90386visible to other modules built with the same -package-name (SwiftPM sets it); between public and internal; no subclassing outside the module

Interview traps

  • “Just use v.count” — a pack has no count or index; iterate (6.0) or expand.
  • some P? is an optional of an opaque type, not an opaque optional: the underlying type is still fixed.
  • defer { await … } in a cancelled task: Task.sleep, network calls inside it fail at once — shield the cleanup.
  • weak let is 6.3, not 6.2 (proposal header).

Remember

“each names it, repeat stamps it, for walks it.”

Likely questions

  1. What do packs replace? — N hand-written overloads per arity.
  2. each vs repeat? — names an element vs stamps a pattern.
  3. Module selector when? — two imports export one name.
  4. Async cleanup? — defer + withTaskCancellationShield.