← Back to list

WWDC26: Iterable — Solving the Sequence Problem

At WWDC26, Apple introduced one of the most important changes coming to Swift’s ownership system: the new Iterable protocol.

Okan Orkun in iCommunity · 2026-06-20 16:48 · 50 claps · 5.7 min read
#wwdc #ios #swift #ios-development #software-engineering
Open on Medium ↗
Wiki topics: 📱 · Mobile Development

WWDC26: Iterable — Solving the Sequence Problem

At WWDC26, Apple introduced one of the most important changes coming to Swift’s ownership system: the new Iterable protocol.

At first, it may look like just another protocol. But Iterable solves a problem that the existing Sequence protocol cannot solve well. More importantly, it creates new opportunities for iteration optimizations, better memory safety and support for Swift’s new non-copyable types.

Let’s see why we need it.

The Problem with Sequence

For many years, Swift has used the Sequence protocol for iteration. When you write a loop:

for element in collection {
  print(element)
}

Swift creates an iterator behind the scenes and repeatedly calls next() until there are no more elements.

[Sequence Iteration Model]
Collection -> Iterator -> next() -> Element

This design works well for most collections. However, there is one major limitation: The Iterator must return an element every time next() is called.

That means Swift needs to either:

  • Copy the element, or,
  • Move (consume) the element out of the collection.

For normal value types, this is usually fine. But Swift is moving toward a new ownership-based future with features such as non-copyable (~Copyable) and non-escapable (~Escapable) types. These types cannot always be copied or moved freely in memory.

For them, the old iteration model becomes difficult or even impossible.

Enter Iterable

The new Iterable protocol takes a different approach. Instead of exposing iteration only through repeated next() calls, it allows an iterator to return a borrowed view of contiguous elements using Span

What’s Span? Think of a Span as a lightweight view into memory. It does not own the elements and it does not copy them. It simply provides temporary access to elements that already exist elsewhere.

This design works naturally with Swift’s ownership system and enables more efficient iteration for types that store data in contiguous memory.

By embracing this concept, the Iterable protocol signature allows the compiler to enforce strict borrowing rules without making copies:

public protocol Iterable<Element, Failure>: ~Copyable, ~Escapable {
  /// A type representing the iterable type's elements.
  associatedtype Element: ~Copyable

  /// A type representing an error thrown during iteration.
  associatedtype Failure: Error = Never

  /// A type that provides the iteration interface and
  /// encapsulates its iteration state.
  associatedtype IterableIterator: IterableIteratorProtocol<Element, Failure> & ~Copyable & ~Escapable

  /// Returns a borrowing iterator over the elements of this sequence.
  @_lifetime(borrow self)
  func makeIterableIterator() -> IterableIterator

  /// A value less than or equal to the number of elements in the sequence,
  /// calculated nondestructively.
  var underestimatedCount: Int { get }

  /// Internal customization point for fast `contains(_:)` checks.
  func _customContainsEquatableElement(_ element: borrowing Element) -> Bool?
}

// Default implementations
extension Iterable where Self: ~Copyable & ~Escapable, Element: ~Copyable {
  public var underestimatedCount: Int { 0 }
  public func _customContainsEquatableElement(...) -> Bool? { nil }
}

This allows Swift to iterate without creating unnecessary copies.

A Different Mental Model

With Sequence, iteration is a repetitive, one-by-one process:

Collection -> Iterator -> next() -> Element -> next() -> Element

With Iterable, it becomes like that:

Collection -> Iterator -> nextSpan() -> [Element, Element, Element, Element]

With Iterable, iteration can work with contiguous regions of memory instead of always requesting one element at a time.

This gives the compiler more opportunities to optimize iteration while also supporting ownership features such as non-copyable types.

How Does Iterable Work?

The proposal introduces a new iterator protocol called IterableIteratorProtocol.

public protocol IterableIteratorProtocol<Element, Failure>: ~Copyable, ~Escapable {
  /// A type representing the iterated elements.
  associatedtype Element: ~Copyable

  /// A type representing an error thrown during iteration.
  associatedtype Failure: Error = Never

  /// Returns a span over the next group of contiguous elements, up to the
  /// specified maximum number.
  @_lifetime(&self)
  mutating func nextSpan(maximumCount: Int) throws(Failure) -> Span<Element>

  /// Advances this iterator by up to the specified number of elements and
  /// returns the number of elements that were actually skipped.
  mutating func skip(by maximumOffset: Int) throws(Failure) -> Int
}

// Default implementations
extension IterableIteratorProtocol where Element: ~Copyable {
  public mutating func nextSpan() throws(Failure) -> Span<Element> { ... }
  public mutating func skip(by maximumOffset: Int) throws(Failure) -> Int { ... }
}

The iterator returns a Span containing zero or more elements. When the returned Span is empty, iteration is complete.

There is also another method: skip(by:). This allows the iterator to move forward without reading elements. Some data structures can use this to avoid unnecessary work when skipping through data.

What Happens Inside a for Loop?

The beautiful thing is that developers don’t need to learn a new loop syntax. You still write your code the exact same way:

for element in collection {
  process(element)
}

Conceptually, iteration may look something like this:

// A simplified mental model of what iteration could look like:
var iterator = collection.makeIterableIterator()
while true {
    // Fetch a memory chunk instead of copying a single item
    let span = iterator.nextSpan(maximumCount: Int.max)
    if span.isEmpty { break } // Loop ends when the chunk is empty

    // Iterate over the borrowed view directly in memory
    for i in span.indices {
        let element = span[i] // Access through a borrowed view
        process(element)
    }
}

The syntax stays exactly the same. Only the underlying implementation changes. This fulfills one of the main goals of the proposal: Better performance without making code more complicated.

Not Every Type Works the Same Way

Different collections can return spans in different ways depending on how they physically store data in hardware memory:

  • Inline Array: All elements already live next to each other in memory. The iterator can return one large Span containing every element. Only one call to nextSpan() is needed.
[ 1 | 2 | 3 | 4 | 5 ] ➔ (One single Span covering everything)
  • Ring Buffer: Its data may be split into two separate memory regions. The iterator can safely return 2 separate spans.
[ 1 | 2 | 3 ] ➔ (Span 1)
[ 4 | 5 ]     ➔ (Span 2)
  • Range: A Range does not store values contiguously in memory. Instead, values are generated as needed. Because of this, an implementation could choose to return smaller spans than a collection that already stores its elements in memory.

The iterable protocol is flexible enough to support all of these distinct hardware and memory realities seamlessly.

Throwing Iteration

Another interesting addition is support for typed errors during iteration. The proposal introduces a Failure associated type.

Most standard collections use Failure == Never, which means iteration cannot fail. However, some lazy or fallible collections may need to throw errors while producing values. For these types, Swift can now natively support a clean syntax:

for try element in collection {
  print(element)
}

If you have used AsyncSequence before, this error-handling model will feel immediately familiar.

Trade-Offs

Iterable improves ownership safety and creates new optimization opportunities, but it also introduces some limitations.

Because elements are borrowed from the iterator, they cannot always outlive the iterator that produced them. This means that some generic algorithms become harder to express compared to traditional Sequence-based iteration.

This trade-off is intentional. The proposal prioritizes ownership correctness and efficient iteration over unrestricted element lifetimes.

Which Types Will Use Iterable?

The proposal adds Iterable support to several ownership-focused types in the standard library including:

  • Span / MutableSpan
  • RawSpan / MutableRawSpan
  • InlineArray

These types already work closely with contiguous memory layouts making them natural candidates for borrowed iteration.

Traditional collections still use Sequence today, but Iterable represents an important step toward Swift’s long-term ownership-based iteration model.

Why Should Developers Care?

You may never create a non-copyable type yourself. You may never implement an Iterable iterator from scratch. But the feature still matters deeply for everyday development:

  • Better Foundation: It gives Swift a robust foundation for modern, ownership-based programming.
  • Performance Opportunities: It creates new opportunities for the compiler and standard library to optimize iteration.
  • Familiar Syntax: It keeps the clean for-in syntax we all know and love.

Iterable is not a feature that most developers will use directly. Instead, it is an important building block for Swift’s future.

Together with borrowing, consuming, non-copyable types and Span, Iterable helps move Swift toward a safer and more ownership-aware programming model while creating new opportunities for iteration performance improvements.

Sources & Further Reading


메타데이터
post_id
042fe98a5c2f
slug
wwdc26-iterable-solving-the-sequence-problem-042fe98a5c2f
url
https://medium.com/icommunity/wwdc26-iterable-solving-the-sequence-problem-042fe98a5c2f
canonical_url
https://medium.com/icommunity/wwdc26-iterable-solving-the-sequence-problem-042fe98a5c2f
author_url
https://medium.com/@orkunokann
status
ok
fetched_at
2026-06-22 00:13:37