Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

IterableYielded value
ListElement
DictionaryKey, in insertion order
SetElement, with no promised order
RangeInteger of the range’s element type
StringUnicode scalar value as str
bytesByte as u8
std.bytes.BytesByte as u8
GeneratorDeclared 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.