Iterable values and borrowing
for name in iterable: evaluates the iterable once. Owned collections and
generators transfer into the loop’s hidden owner; it drops on exhaustion or
early exit. There is no implicit snapshot. Ranges, strings, and copyable
elements retain their ordinary copy semantics.
Iterating a range, as the source of for, reversed, enumerate, zip, a
comprehension, or a generator expression, compiles to a counted loop. The
element count is computed once, before the first iteration, and no step calls
the runtime, so it costs no more than the equivalent while loop. This holds
for a range(...) call written directly as the source and for a range value
stored in a variable, field, or generator frame or returned by a call. The loop
copies the range when it starts: changing a bound variable, or assigning a new
range to the variable, inside the body does not change the count. A generator
expression’s outer source is stored in its frame, so each element also costs
one frame resume.
| Iterable | Yielded value |
|---|---|
| List | Element |
| Dictionary | Key, in insertion order |
| Set | Element, with no promised order |
| Range | Integer of the range’s element type |
| String | Unicode scalar value as str |
bytes | Byte as u8 |
std.bytes.Bytes | Byte as u8 |
| Generator | Declared yielded value |
Consuming list iteration transfers affine elements. for item in &list
preserves the list and always yields &T, including scalars and strings.
for item in &mut list yields &mut T, including scalar elements.
Existing collection references follow the same access mode. This includes
direct reference-returning calls: for item in owner.column()
and iter(owner.column()) need no intermediate reference binding. The call
runs once when the source is prepared, and its returned loan protects the owner
through the loop, including backedges, against
invalidating mutation, movement, or destruction. Element loans follow the
borrowing rules: only distinct literal list
indices are disjoint. Iteration
references cannot escape into stored references.
Borrowed temporary iterable owners remain alive through the loop and drop when its scope exits, including on early exit.
Borrowed dictionary and set iteration yields shared references to keys or
elements. Borrowed range, string, and bytes iteration also yields shared
references; each refers to a computed value in an iteration-local slot and
cannot escape that iteration. A Bytes buffer has addressable storage:
for b in &buffer and for b in &mut buffer loop over a
slice view of the whole buffer and
yield &u8 or &mut u8 into it. Comparisons and printing observe references uniformly;
dereference explicitly when passing an element to a function that takes a value
or storing it in a new collection. Other arithmetic observes numeric references
as well.
Borrowed generators cannot be loop sources; advance one with next(&mut g)
instead. Use for item in copy(values)? for an explicit independent snapshot
when the original must be mutated during iteration.
for key, value in dictionary.items(): borrows a named dictionary or field
and preserves insertion order. Keys are &K and values are &V, regardless
of whether their types are copyable. (&mut dictionary).items() or
&mut dictionary.items() instead yields &mut V values with shared &K
keys. These loops allocate
no item tuples or snapshot buffers. items() is a loop/comprehension intrinsic,
not a storable iterator, and requires two distinct binding names (or _).
Its source loan prohibits invalidating dictionary mutation.
dictionary.keys() creates a bindable local KeyView[K]; iteration yields
&K in the same insertion order, without allocation. dictionary.values()
is a loop/comprehension source yielding &V, including affine values;
(&mut dictionary).values() yields &mut V; see
updating every value.
Both protect the whole dictionary under their loans. Collect explicitly ([*key for key in d.keys()]?), or
use keys_snapshot() / values_snapshot() for owned lists and into_keys() /
into_values() to consume the owner. See dictionary views
for the key view’s set operations and storage restrictions.
Loop targets permit recursive tuple unpacking, such as for (x, y), label in rows.
Comprehensions and generator expressions accept the same targets. Each name is
a new immutable binding local to that iteration scope, shadowing any outer local
without changing it. A clause evaluates its source before introducing its own
bindings; filters and subsequent sources see the new bindings. Enclosing tuple
parentheses do not change the shape: for ((x, y), label) in rows is equivalent,
including over borrowed zip and dictionary item sources. Over borrowed elements, each name
borrows its component in place: for n, s in &pairs binds n as &item[0]
and s as &item[1], and for n, _ in &mut pairs makes n a &mut
reference, so *n += 1 changes the list. The names borrow distinct fields of
the element, so swap(n, s) under for n, s in &mut pairs passes both as
&mut arguments, as swap(&mut p[0], &mut p[1]) does for an element p of a
&mut loop. Borrowing the list or another element while they are live
conflicts. Iteration itself, including string
scalar production and generator next, adds no allocation; iterable creation
and body expressions keep their own contracts. There is no arbitrary user
iterator protocol, special-method dispatch, or async iteration.
Generator expressions construct
lazy owned frames and follow the generator iteration rules.