Stop Scanning On Found
ID |
swift.stop_scanning_on_found |
Severity |
low |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Efficiency |
Language |
Swift |
Tags |
efficiency, performance, suspicious-code |
Description
Reports a for-style loop whose body sets a result variable via a
plain = assignment and keeps iterating without break, return,
or throw. The shape is the classic manual "find the first element
matching a predicate" implemented as a full scan — every later
matching element overwrites the same slot, so the work after the
first match is wasted.
var found: Element? = nil
for x in items {
if predicate(x) {
found = x // FLAW — keeps iterating after the first match
}
}
Rationale
When the assignment is to a variable declared in an enclosing
scope, the only effect of subsequent iterations is to replace the
stored value with one of equal "match-ness". The author almost
always meant "the first match" — break (or return) makes
that intent explicit and turns an O(n) loop into early-exit.
Swift’s first(where:) already encapsulates this short-circuit.
Remediation
Add a break after the assignment, or use first(where:):
// 1) break right after the assignment
var found: Element? = nil
for x in items {
if predicate(x) {
found = x
break
}
}
// 2) use the standard-library short-circuit form
let found = items.first(where: predicate)
If the loop is meant to collect every match, use an array
accumulator instead — result.append(x) or result += [x]
is unambiguous and the rule will not fire on it.
When not to fire
The rule is intentionally conservative and skips:
-
compound-assign operators (
+=,-=, …) — those are accumulation, not "found"; -
member or subscript writes (
self.x = …,dict[k] = v) — those are mutations of a structured target; -
variables declared inside the loop body — those are loop-local state, not an outer "result" slot;
-
loops whose body already contains
break,return,throw, orcontinueafter the assignment; -
the same variable being mutated elsewhere in the loop via
+=/.append(…)/.insert(…).