Source-linked AI summary

Pack Iteration in Swift: Ordinary Control Flow for Variadic Generics

Serafima Nerush, Eitan Frachtenberg

arXiv:2609.28822v1cs.PLcs.SE

TL;DR

Variadic generics are difficult to use naturally when pack access depends on recursive decomposition or expression-only expansion. Pack Iteration makes a pack expansion an ordinary Swift for-in loop with on-demand element evaluation. It preserves performance in the reported evaluation and sometimes reduces run time or binary size.

  • Problem

    Swift pack elements were accessible only through expansion expressions, while C++ pack processing often relied on complex recursive decomposition, making variadic generics difficult to use naturally.

    Source: §1 · p. 1, §2.2.1 · p. 2
  • Method

    Pack Iteration uses a pack expansion as a for-in loop source, opening one element into a scalar generic context on each iteration.

    Source: §1 · p. 1, §5 · p. 11
  • Results

    Up to 54.4% reduction in run time or 17.6% reduction in binary size was reported in several benchmarks, without a performance penalty overall.

    Source: §5 · p. 11
  • Takeaways & Limitations

    Direct iteration can replace workaround patterns such as local helper functions and throwing control flow while preserving performance in the reported evaluation.

    Source: §5 · p. 11

    The Sequence-based for-in approach is constrained by type-checking order: the opened element environment needed after a where clause may not be ready when that clause is checked.

    Source: §4.5 · p. 11

Abstract

from arXiv · show

Variadic generics are a powerful tool for type-safe meta-programming. Yet in most widely used languages, they remain an "expert-only" feature due to their reliance on complex patterns such as recursive decomposition or expansion expressions that do not compose naturally with ordinary control flow. In C++, for example, accessing elements of parameter packs has traditionally relied on unintuitive recursive patterns that "peel off" elements. This paper presents Pack Iteration, a feature introduced in Swift 6.0 that allows developers to iterate over parameter packs using a familiar, imperative for-in loop. By treating pack expansion as a first-class source for iteration, Swift bridges the gap between high-level expressiveness and advanced generic programming. We detail the design and implementation of this feature within the Swift compiler, focusing on the challenges of bridging static type-checking in the constraint system with dynamic execution in the Swift Intermediate Language. Unlike traditional models that expand packs at compile time, Swift's implementation supports on-demand evaluation, enabling efficient dynamic iteration and short-circuiting control flow. Our empirical evaluation confirms that pack iteration provides performance comparable to - and sometimes significantly better than - the complex workarounds previously required.

1 Introduction

Variadic generics can express type-safe abstractions over unknown arities, but existing pack-processing models make them difficult to use naturally. The paper presents Swift Pack Iteration as an ordinary for-in loop over pack elements, with familiar control flow and on-demand execution.

1 Introduction

  • Swift pack elements were accessible only through expansion expressions, while C++ pack processing commonly required recursive “peeling” of elements.The paper also notes that these models make variadic generics complex for many programmers.Source: §1 · p. 1
  • Pack Iteration makes a pack expansion an ordinary iteration source, binding elements locally and supporting guard, break, and continue.The paper frames the difficulty as a problem with the programming model, not variadic generics themselves.Source: §1 · p. 1
  • Pack iteration uses dynamic, on-demand evaluation, allowing short-circuiting without eagerly processing every pack element.The paper presents its design and evaluates its first full implementation in Swift.Source: §1 · pp. 1–2

2 Background and Related Work

The section reviews generic programming models, variadic generics, and the limitations of existing ways to process parameter packs. It positions Swift pack iteration against compile-time expansion and expression-based access.

2 Background and Related Work

  • The section reviews Swift generics and compares language models to frame how parameter packs can be processed.The review covers Swift, C++, and Java.Source: §2 · p. 2

2.1 Generics

  • Swift combines separate type-checking with runtime support for unspecialized generic code, using runtime metadata to manipulate values without knowing their layout.Specialization may eliminate runtime overhead as an optimizer step, but is not required for correctness or separate compilation.Source: §2.1 · p. 2

2.2 Variadic Generics

  • Variadic generics abstract over any number of types while retaining their identities, supporting variable-arity APIs such as tuple operations.In practice, parameter packs represent a list of types or values.Source: §2.2 · p. 2

2.2.1 Variadic Templates in C++.

  • C++ variadic templates produce compile-time specializations; pack processing therefore uses expansion rather than traversal, commonly via recursive decomposition.This style can be efficient but makes many pack-processing tasks difficult to read and express directly.Source: §2.2.1 · p. 2

2.3 Parameter Packs in Swift

  • Swift’s `each` syntax forms type parameter packs, and `repeat` expands a pattern for each substituted pack element.The paper identifies expression-only access to pack values as the motivation for adding iteration.Source: §2.3 · p. 3

3 Pack Iteration in Swift

The section describes the primary motivation and implementation of parameter-pack iteration in Swift.

3 Pack Iteration in Swift

  • The section details the primary motivation and implementation of parameter-pack iteration in Swift.Source: §3 · p. 3

3.1 The Problem

Swift’s expression-only pack expansions make short-circuiting awkward: expansion evaluates every element, so programmers resort to throwing helpers and do-catch control flow.

3.1 The Problem

  • Expanding a repetition pattern evaluates every pack element, making short-circuiting difficult to express directly.Source: §3.1 · p. 3
  • A workaround puts statement-based processing in a throwing function and catches an error to stop the operation.The example throws when tuple elements differ, then catches the error to return false.Source: §3.1 · p. 3
  • Expression-only semantics and the lack of first-class short-circuiting make Swift variadic generics an advanced feature for expert programmers.Source: §3.1 · p. 3

3.2 Introducing Pack Iteration

Pack iteration makes a pack expansion a source for ordinary for-in control flow, enabling direct per-element binding and early exits. The design preserves each element’s static type and constraints while supporting pattern matching and more advanced heterogeneous-pack operations.

3.2 Introducing Pack Iteration

  • Tuple equality binds corresponding elements from two packs as a local pair, then returns false at the first mismatch or true after the loop.This replaces a nested throwing helper with ordinary guard and return control flow.Source: §3.2 · pp. 3–4
  • Swift for-in loops can iterate over pack expansion expressions, composing pack traversal with familiar loop patterns.Pack iteration uses existing for-in functionality.Source: §3.2 · p. 3, §3.2.1 · p. 4

3.2.1 Language Design.

  • On each iteration, a local variable takes the current pack element’s scalar type, preserving the type pack’s requirements when present.For a pack containing Int, String, and Bool, the scalar type is substituted successively with those types.Source: §3.2.1 · p. 4
  • The for case pattern matches each pack element and evaluates its repetition pattern only when that iteration is reached, leaving later patterns unevaluated after an early exit.The loop body runs only for elements matching the specified case.Source: §3.2.1 · p. 4
  • Pack iteration filters successful Result values and evaluates their producers while preserving each element’s static type and protocol conformance.This avoids losing ValueProducer conformance as would happen with a heterogeneous [Any] collection.Source: §3.2.1 · p. 5

3.3 Implementation Details

Pack iteration required changes across Swift’s type-checking and SIL-generation stages to make pack expansions behave as for-in sources while preserving iteration semantics. The implementation gives loop bindings scalar element types, tracks nested pack contexts, and lowers control flow with a dynamic index and a shared loop latch.

3.3.1 Overview.

  • Pack iteration required compiler changes from semantic analysis through SIL generation to preserve Sequence-like loop semantics for a distinct language construct.The compiler distinguishes Sequence sources from pack expansions and incrementally evaluates pack elements during lowering.Source: §3.3.1 · p. 5, §3.3.2 · p. 5

3.3.2 Semantic Analysis.

  • The constraint system derives an expansion element type and checks the loop pattern against it, while opened-element environments give loop bindings scalar types.The compiler records the opened environment for declarations in the loop body, so an element such as t is checked as scalar T′ rather than as a pack.Source: §3.3.2 · pp. 5–6

3.3.3 Nested Iteration.

  • Nested pack loops push and pop opened-element environments, preserving access to outer-loop bindings inside inner loops and after their scope ends.Ordinary Sequence loops do not change the environment stack; Table 1 traces the nested-loop stack states.Source: §3.3.3 · p. 6, Table 1 · p. 7

3.3.4 SIL Generation.

  • SILGen lowers a pack expansion with a dynamic index, checks pack length, opens and binds each element, then executes the loop body.The implementation also uses existing for-in destinations for break and continue, and the resulting SIL proceeds to optimization.Source: §3.3.4 · p. 7
  • The loop’s pattern-matching form executes its body only for matching elements, so the failure path must rejoin iteration through the latch.The example matches the first pack element and skips the second; both normal execution and match failure reach the shared index-advancement block.Source: §3.3.4 · p. 7, Figure 3 · p. 8
  • A loop latch ensures both successful body execution and pattern-match failure advance the dynamic pack index instead of restarting iteration.Without the latch, pattern-match failure branches back to setup, reinitializes the index, and can cause an infinite loop.Source: §3.3.4 · p. 7, Figure 3 · p. 8

4 Evaluation

The evaluation demonstrates pack iteration’s expressive uses, compares it with emerging C++ and Rust approaches, and reports performance benchmarks and remaining limitations. The benchmarks find comparable compile times overall, with runtime and binary-size gains in selected cases.

4.1 New Functionality

  • Pack iteration operates on heterogeneously typed arrays and can check that all are empty, returning early at the first non-empty array.The evaluation presents this as a succinct operation enabled by on-demand evaluation.Source: §4.1 · p. 8

4.2 Performance

  • Compile times remain comparable at 1,000 elements; Tuple and Closure slow by 1.8%, while Lookup and Result speed up by 2.2% and 3.6%, respectively.The Closure benchmark improves by only 0.2%; closure capture and indirect dispatch are described as bottlenecking gains from loop lowering.Source: §4.2 · pp. 8–9, Figure 4 · p. 9
  • At 1,000 elements, Result runtime falls from 1.14 s to 0.52 s, a 54.4% reduction.The paper attributes the reduction to removing an extra layer of per-element dispatch, reducing overhead introduced by existentials.Source: §4.2 · pp. 8–9, Figure 5 · p. 9
  • At 1,000 elements, Tuple binary size drops from 102 KB to 84 KB, a 17.6% reduction.The “Before” version uses an auxiliary Error type, a throwing helper, and do/catch to simulate short-circuiting; the “After” version uses pack iteration and guard.Source: Figure 6 · p. 9, §4.2 · p. 9

4.3 Comparison to Emerging C++ and Rust Features

  • Swift’s sequential, on-demand pack loop differs from C++26’s compile-time expansion statements and aligns with Rust discussions of more direct variadic operations.The cited Rust designs emphasize compile-time reasoning over tuple-like variadic structure.Source: §4.3 · p. 10

4.4 Community Review and Language Evolution

  • Swift Forum feedback described Pack Iteration as intuitive and natural, and as a missing usability layer for variadic generics.The paper contrasts Swift’s public community review with WG21’s standards-focused process.Source: §4.4 · pp. 10–11

4.5 Limitations and Future Work

  • Pack iteration lacks Sequence-style where clauses; supporting them requires resolving when the loop’s opened element environment is available for type-checking.The proposed future direction is to defer where-clause type-checking until the pattern environment is established.Source: §4.5 · p. 11

4.5.2 Single-lane Assignment.

  • A loop cannot initialize a single lane of a pack; this restricts element-by-element construction of a same-shape tuple from a value pack.The paper suggests a future definite-initialization extension may lift this restriction because the iteration shape is statically known.Source: §4.5.2 · p. 11

5 Conclusion

Pack Iteration addresses the awkward workarounds used for variadic-generic programming by making pack expansions iterable with ordinary loop control flow. Its on-demand implementation preserves Swift’s generic model, while evaluation reports no performance penalty and improvements in some benchmarks.

5 Conclusion

  • Pack expansion as a for-in source opens one pack element per iteration into a scalar generic context, supporting local bindings, pattern matching, break, continue, and early return.Source: §5 · p. 11
  • Compiler semantic analysis records the opened element environment, while SILGen lowers pack iteration to a dynamic loop that evaluates elements on demand rather than eagerly expanding the pack.This preserves Swift’s separately type-checked generic-code model.Source: §5 · p. 11
  • Up to 54.4% reduction in run time and up to 17.6% reduction in binary size occur in several benchmarks; pack iteration also improves expressiveness and readability without a performance penalty.The direct iteration form eliminates workaround patterns such as local helper functions and throwing control flow.Source: §5 · p. 11
  • C++26 expansion statements and Rust discussions such as static for reflect broader interest in loop-like operations over heterogeneous type and value sequences, despite differing generic models and implementations.Source: §5 · p. 11
  • Swift integrates parameter packs with a familiar for-in loop while preserving static type information, on-demand execution, and compatibility with separately type-checked generic code.Source: §5 · p. 11
Loading 2609.28822v1…