Skip to main content

of

of introduces value-iteration in for loops — for value of collection — complementing for ... in ... which iterates keys/elements. Where for key in map yields keys, for value of map yields values; for value-iterable collections (maps, iterables with a dedicated value channel) of selects the value projection. It is reserved by the lexer; parser support is focused on iteration forms.

Ground truth: Lexer TokenKind::Of (src/parsing/lexer.rs:943, keyword "of"), hover doc Iterator-style for ... of ... loop over iterables (als/src/hover.rs:349), loop family with for/in (control-flow.md:2.2). of is context-sensitive outside loops.


Syntax

for_in_stmt ::= "for" binding "in" iterable block
for_of_stmt ::= "for" binding "of" iterable block // value projection

binding ::= ident | "(" ident ("," ident)* ")" | ident "," ident
iterable ::= expr
block ::= "{" statements "}"

of is reserved (TokenKind::Of). It only has special meaning inside a for header immediately after the binding; elsewhere it is not an operator.

Canonical forms

for value of map { print(value); } // value iteration over a map
for v of [10, 20, 30] { print(v); } // value iteration over array (similar to `in` for arrays)
for (k, v) of entries { print(k, v); } // when entries yield pairs and value is destructured

// Contrast with `in`
for key in map { print(key); } // keys
for value of map { print(value); } // values
for item in [10, 20, 30] { print(item); } // elements (arrays: in ≈ of)

in vs of — when to use which

FormBinds toIterable exampleUse when
for x in xselement / key / entry keyarray, range, map keys, string charsYou need keys or elements
for x of xsvaluemap values, value-iterableYou need the value channel
for (k,v) in xsdestructureentries / enumerate()You need both
value in xsmembership testarray/set/mapMembership, not iteration (keyword in as operator)

For plain arrays and ranges, in and of are often interchangeable (both yield elements). For maps and keyed collections the distinction matters. If of is not yet fully wired in your build, for value in map.values() is the explicit fallback.


Semantics

Value projection

for pat of iterable { body } evaluates iterable once, obtains a value-iterator (values() / into_value_iter() per stdlib), and binds pat to iter.next() each iteration:

// Conceptual desugaring
let __iter = iterable.into_value_iter(); // or .values() / .value_iter()
while let Some(pat) = __iter.next() { body }

This contrasts with for ... in ... which uses into_iter() (key/element iterator). For ranges and arrays the two iterators coincide; for maps/ordered maps they diverge.

Scoping and control flow

  • Loop variable is scoped per iteration and does not leak.
  • break exits, continue skips to next value, return/throw exit the enclosing function (running defer handlers).
  • defer inside the body runs once per iteration before the next next() or before loop exit.

Ownership and mutation

The value iterator may borrow or snapshot values depending on backend. For collections with reference-type values (objects, arrays), of typically yields a borrowed or shared reference (share/strong semantics may apply). Mutating the collection's size while a value iterator is live is rejected or raises a runtime error — snapshot first if mutation is needed:

let snapshot = [...map.values()]; // copy values to array, then iterate
for v of snapshot { /* may mutate map */ }

Relation to iterator protocol

HeaderDesugars toOptimizations
for x in 0..Ncounter loop (CMP/JGE/INC)No allocation, register counter
for x in arrayinto_iter().next() or pointer bumpBounds-checked
for x of mapinto_value_iter().next()Hash/value array walk

Compilation pipeline

  1. Lex ofTokenKind::Of.
  2. Parse — inside for header: ForOf { binding, iterable, body }; outside: treated as identifier/contextual (no operator).
  3. HIRHirStmt::ForOf with value-iterator target.
  4. Desugar/CFG — emit while let with value-iterator.
  5. Codegen — inline value walk for maps / pointer bump for arrays.

Examples

Example 1 — of over maps and value collections

fn map_values_demo() {
let scores = { "alice": 10, "bob": 20 };

// `in` yields keys
for key in scores { print("key:", key); } // "alice", "bob"

// `of` yields values
for score of scores { print("score:", score); } // 10, 20

// To get both key and value, iterate entries and destructure
let entries = [["alice", 10], ["bob", 20]];
for (name, score) in entries { print(name + "=" + str(score)); }

// Array value iteration — similar to `in` for arrays
for v of [10, 20, 30] { print(v); } // 10,20,30

// String — for...of yields values (chars) where `in` and `of` coincide
for ch of "hi" { print(ch); } // "h","i"
}

// Fallback when `of` is not fully wired — explicit values()
fn map_values_fallback() {
let m = { "x": 1, "y": 2 };
for v in m.values() { print(v); } // 1, 2 — equivalent to `for v of m`
}

Example 2 — Filtering, early exit, and defer inside of loops

fn sum_values(map): i32 {
let total = 0;
for v of map {
if v < 0 { continue; } // skip negative values
total += v;
if total > 100 { break; } // early exit
}
return total;
}
print(sum_values({ "a": 10, "b": -5, "c": 40, "d": 80 })); // e.g., 50 or 130 depending on iteration order

fn with_defer_over_values(paths: [string]) {
for p of paths {
let f = FS.open(p, "w");
defer f.close(); // runs each iteration before next value
f.write("hello " + p);
if p == "stop" { break; } // defer still runs for this iteration
}
}

fn enumerate_values(xs: [i32]) {
// When you need both index and value, `enumerate()` + `in`/`of` destructure
for i, v of xs.enumerate() { print(f"{i}:{v}"); }
// or
for (i, v) in xs.enumerate() { print(i, v); }
}

Example 3 — Async, concurrent spawn, and region with value iteration

async fn process_values(urls: [string]): [string] {
let results: [string] = [];
// `for ... of ...` with async suspension — sequential
for url of urls {
let body = await fetch(url);
results.push(body);
}
return results;
}

async fn process_values_concurrent(urls: [string]): [string] {
let handles = [];
for url of urls { handles.push(spawn fetch(url)); } // spawn inside of-loop
let out: [string] = [];
for h in handles { out.push(await h); } // join
return out;
}

// Region bulk-free per value iteration
fn batched_from_map(map) {
for v of map {
region scratch {
unsafe {
let buf: *mut u8 = alloc<u8>(256);
// use v and buf together
for i in 0..256 { buf[i] = (v as u8 + i as u8) as u8; }
// bulk-free at end of this value's iteration
}
}
}
}

Restrictions / Errors

KindTriggerDiagnosticHelp
Lexicallet of = 1unexpected token 'of', expected identifierTokenKind::Of reserved (lexer.rs:943)
Parsefor x of { } with missing iterableexpected expression after 'of'Provide for v of collection { }
Parsefor x of outside for header (e.g., let a = b of c)unexpected token 'of' / expected operatorof only has special meaning inside for
Typefor v of 42 (non-iterable)type 'i32' is not value-iterableUse array/map/iterable with value channel
Typefor (a,b) of xs where value not 2-tuplecannot destructure <type> into 2 bindingsFix pattern arity
Backendof on build without value-iterator wiringfor ... of ... is not supported for this typeUse for v in xs.values() fallback
BorrowMutating collection while of iterator livecannot mutate collection while iteratingSnapshot: [...xs.values()] first
RegionRegion pointer from of body escapes regionallocation from region does not outlive regionCopy data out

Common pitfall — using of as a general operator:

// ❌ Not valid: `of` is not a binary operator like `in`
let a = xs of ys; // parse error

// ✅ Only inside `for`:
for v of xs { print(v); }

// For membership tests, use `in`:
if 3 in [1,2,3] { print("yes"); }
if "a" in map { print(map["a"]); }

See Also

  • for — loop header and ForIn/ForOf desugaring
  • infor ... in ... and membership value in collection
  • while / do — condition-based loops
  • break / continue — loop control (JUMP_OUT/JUMP_LOOP_HEAD)
  • Control Flow — iterator protocol and loop lowering
  • Arrays & Tuples — arrays and iteration
  • Collections — built-in collection data structures
  • src/parsing/lexer.rs:943, als/src/hover.rs:349