How to Write a For Loop in Swift 3 for an Array You Modify During Iteration
To safely modify an array while iterating in Swift 3, avoid standard for-in loops and instead use an index-based while loop, iterate over a copy, or use enumerated() for in-place mutations.
When you write a for loop in Swift 3 for an array that you modify during the for loop, you must avoid the standard for-in syntax. The Swift compiler implements for-in as fast enumeration over the Sequence protocol, obtaining an iterator once before the loop begins. Mutating the underlying collection during iteration invalidates this iterator, leading to undefined behavior or runtime crashes.
Why You Cannot Mutate an Array During a Standard For-In Loop
In Swift 3, the for ... in construct relies on fast enumeration over any type conforming to Sequence. According to the Swift compiler source in lib/ASTGen/Sources/ASTGen/Stmts.swift, specifically the generate(forStmt:) method, the compiler rewrites the loop into calls to the collection’s iterator. Because the iterator is obtained once before the loop starts, mutating the underlying array while the iterator is active invalidates the iteration state. This yields undefined behavior: elements may be skipped, or your app may crash with an iterator invalidation error.
Safe Patterns to Modify an Array While Iterating in Swift 3
To safely write a for loop in Swift 3 for an array you modify during the loop, use one of these four idiomatic approaches. Each avoids iterator invalidation by either decoupling the iteration from the mutation or by using index-based access that bypasses the Sequence iterator.
Iterate Over a Copy of the Array
Create a snapshot of the array before looping. Mutations apply to the original array, while the loop iterates over the immutable copy.
var numbers = [1, 2, 3, 4, 5]
let snapshot = numbers // copy of the current state
for n in snapshot {
if n % 2 == 0 { // remove even numbers from original
numbers.removeAll(where: { $0 == n })
}
}
print(numbers) // → [1, 3, 5]
This technique is best when the array fits comfortably in memory and you need to remove elements based on a condition.
Use an Index-Based While Loop
Manually manage an integer index to traverse the array. This allows you to insert or remove elements and adjust the index accordingly to avoid skipping elements or accessing out-of-bounds indices.
var items = [10, 20, 30, 40]
var i = 0
while i < items.count {
if items[i] == 20 {
items.append(25) // add a new element
}
if items[i] == 30 {
items.remove(at: i) // remove current element
continue // skip i+=1 because array shifted left
}
i += 1
}
print(items) // → [10, 20, 25, 40]
This approach is essential when you need to insert or delete elements at arbitrary positions during iteration.
Modify Elements In-Place with enumerated()
Use enumerated() when you only need to update existing elements without changing the array’s length. This provides both the index and the element, allowing in-place mutation.
var letters = ["a", "b", "c"]
for (idx, ch) in letters.enumerated() {
letters[idx] = ch.uppercased() // modify element in place
}
print(letters) // → ["A", "B", "C"]
Note that enumerated() is safe for value updates but will crash if you attempt to append or remove elements during the loop.
Control Iteration with stride
For explicit index control similar to C-style loops, use stride or a range to access elements by index. This recomputes the count each pass or stores it once, giving you direct array access.
var numbers = [1, 2, 3, 4, 5]
for idx in stride(from: 0, to: numbers.count, by: 1) {
if numbers[idx] < 3 {
numbers[idx] *= 10
}
}
print(numbers) // → [10, 20, 3, 4, 5]
This pattern is useful when you need to skip indices or modify the loop variable based on runtime conditions.
Key Source Files in the Swift Repository
Understanding the implementation details helps explain why these patterns are necessary. The Swift compiler and standard library source code reveals how iteration and mutation interact.
lib/ASTGen/Sources/ASTGen/Stmts.swift: Contains thegenerate(forStmt:)method that transformsfor-inloops into iterator-based code. This shows how the compiler obtains the iterator once before the loop begins, making mutation unsafe.stdlib/public/core/Array.swift: Implements theArraytype, including subscripting and mutating methods. This file demonstrates how arrays provide fast random access, making index-based loops efficient.lib/Sema/SyntacticElementTarget.cpp: Defines theForStmtsyntax node used by the parser, showing the AST structure of loop statements.test/SILGen/sil_locations.swift: Contains unit tests for loop generation, includingtestForStmt(), which exercises the compiler’s SIL generation for loops.
Summary
- Never mutate an array during a standard
for-inloop in Swift 3. The compiler generates an iterator once at the start inlib/ASTGen/Sources/ASTGen/Stmts.swift; mutating the array invalidates this iterator and causes undefined behavior. - Iterate over a copy when you need to remove elements based on conditions and the array fits in memory.
- Use an index-based
whileloop when inserting or deleting elements at arbitrary positions, manually adjusting the index after mutations. - Use
enumerated()only for in-place value updates, not for changing the array length. - Use
strideor range-based loops for explicit index control similar to C-style for loops.
Frequently Asked Questions
Can I use for-in to remove elements from an array in Swift 3?
No. Using for-in to remove elements directly from the array being iterated causes iterator invalidation. The compiler generates code that calls makeIterator() once before the loop starts in lib/ASTGen/Sources/ASTGen/Stmts.swift. Removing elements during iteration corrupts the iterator state, leading to skipped elements or runtime crashes. Instead, iterate over a copy or use an index-based loop.
What happens if I modify an array during fast enumeration?
Modifying an array during fast enumeration produces undefined behavior. The Swift 3 compiler implements for-in as fast enumeration over the Sequence protocol, obtaining an iterator once before entering the loop body. As implemented in stdlib/public/core/Array.swift, mutating the collection while an iterator is active invalidates the iteration state. Your program may skip elements, process elements twice, or crash with a memory access error.
Is enumerated() safe for all types of array modifications?
No. The enumerated() method is only safe for in-place mutations where you update existing elements without changing the array's length. The method returns a sequence of tuples containing the index and element, but the underlying array still uses the same iterator mechanism. You can safely modify array[idx] = newValue, but calling append(), remove(), or insert() during an enumerated() loop invalidates the iterator and causes undefined behavior.
Where is the for-in loop implemented in the Swift compiler?
The for-in loop is implemented in the Swift compiler's AST generation phase. The primary file is lib/ASTGen/Sources/ASTGen/Stmts.swift, which contains the generate(forStmt:) method. This method transforms the for-in syntax into iterator-based code that calls makeIterator() once and then repeatedly calls next() until the sequence is exhausted. The parser defines the ForStmt syntax node in lib/Sema/SyntacticElementTarget.cpp, and the SIL generation is tested in test/SILGen/sil_locations.swift.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →