Skip to main content

in

in has two related roles: (1) the iteration clause of for loops — for binding in iterable — where it binds each element from the iterable to the loop variable, and (2) the membership / containment operatorvalue in collection / key in map — that tests whether a value is present. Both forms derive from the iterator/containment protocol and share the keyword token.

Ground truth: Lexer TokenKind::In (src/parsing/lexer.rs:942, keyword "in"), hover doc Used in for ... in ... loops and ... in collection membership checks (als/src/hover.rs:348), loop desugaring in control-flow.md:2.2.


Syntax

for_in_stmt ::= "for" binding "in" iterable block
binding ::= ident | "(" ident ("," ident)* ")" | ident "," ident
iterable ::= expr

membership ::= expr "in" expr // value in collection / range
// also: key in map / element in array / substring in string

in is reserved (TokenKind::In). It never stands alone — it always sits between a binding/value on the left and an iterable/collection on the right.

Canonical forms

for item in [1, 2, 3] { print(item); }
for key in map { print(key); } // key iteration
for i in 0..5 { print(i); } // range iteration
for (k, v) in entries { print(k, v); } // destructure

if "hello" in greeting { print("found"); } // membership (where supported)
if 3 in [1, 2, 3, 4] { print("yes"); }
if key in map { print(map[key]); }
for ch in "hello" { print(ch); } // string iterable

in vs of

FormWhat it binds / testsTypical iterable
for x in xselements / keysarray, range, map keys, set, string chars
for x of xsvaluesmap values, value-iterable collections
value in collmembership booleanarray, map (key), range, string

Check backend coverage for of and in as pure membership operator — for ... in ... is the universally supported form.


Semantics

As loop clause: for ... in ...

for pat in iterable { body } evaluates iterable once, obtains an iterator (or range counter), and repeatedly binds pat to iter.next() until exhaustion. Desugared:

// Iterable path
let __iter = iterable.into_iter();
while let Some(pat) = __iter.next() { body }

// Range path (optimized — no heap allocation)
let __end = N; let pat = 0; while pat < __end { body; pat += 1; }

Bindings are scoped per iteration; mutating the pattern inside the body does not affect iteration state.

As membership operator: value in collection

Where supported, value in collection evaluates to bool by delegating to a contains protocol:

  • Array / set: linear or hash containment ([1,2,3].contains(2)).
  • Map: key presence ("a" in {"a": 1}true).
  • Range: bounds check (3 in 0..10true, 10 in 0..10false for exclusive ..).
  • String: substring search ("ell" in "hello"true in builds that enable it).

Membership is non-mutating and does not advance any iterator. For maps, prefer key in map before map[key] to avoid missing-key traps.

Performance

  • for x in 0..N: O(N) counter increments, no allocation — the fastest loop form.
  • for x in array: O(N) pointer bump with bounds check per element.
  • x in array (linear array): O(N) scan; x in set / x in map: O(1) hash lookup.

Compilation pipeline

  1. Lex inTokenKind::In.
  2. Parse — inside for header as part of ForIn; outside as binary In expression (membership).
  3. HIR — loop: HirStmt::ForIn; membership: HirExpr::In { lhs, rhs }contains call.
  4. CFG/Desugar — loop lowered to while let or counter; membership to method call / intrinsic.
  5. Codegen — range: registers; array: pointer loop; map/set: hash probe.

Examples

Example 1 — for ... in ... over arrays, ranges, and destructuring

fn sum(xs: [i32]): i32 {
let total = 0;
for v in xs { total += v; }
return total;
}
print(sum([1, 2, 3])); // 6

// Range iteration with inclusive/exclusive bounds
for i in 0..3 { print(i); } // 0,1,2
for i in 0..=3 { print(i); } // 0,1,2,3

// Enumerate with destructuring (index + value)
for i, v in [10, 20, 30].enumerate() {
print(f"{i}:{v}");
}
// 0:10 1:20 2:30

// Destructure pair entries
let pairs = [["a", 1], ["b", 2]];
for (k, n) in pairs { print(k + "=" + str(n)); }

Example 2 — Maps, sets, strings, and of contrast

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

// `for ... in ...` on a map yields keys (per runtime spec)
for key in scores { print(key); } // "alice", "bob", "carol"

// `for ... of ...` yields values (reserved form)
for value of scores { print(value); } // 10, 20, 30

// To get both, iterate entries (shape depends on stdlib)
let entries = [["alice", 10], ["bob", 20]];
for (name, score) in entries { print(name, score); }

// String iteration
for ch in "Adesh" { print(ch); } // "A","d","e","s","h"
}

// Membership tests (availability per backend; pattern is idiomatic)
fn membership_demo() {
let xs = [1, 2, 3, 4];
if 3 in xs { print("3 is in xs"); }
if 9 in xs { print("unreachable"); } else { print("9 not in xs"); }

let map = { "x": 1, "y": 2 };
if "x" in map { print(map["x"]); } // safe — key presence check before access

if 5 in 0..10 { print("in range"); } // true
if 10 in 0..10 { print("no"); } else { print("10 is exclusive end"); }
}

Example 3 — in with control flow, async, and regions

fn filter_with_continue(xs: [i32]): [i32] {
let out: [i32] = [];
for v in xs {
if v < 0 { continue; }
if v > 100 { break; }
out.push(v);
}
return out;
}
print(filter_with_continue([5, -1, 50, 200, 3])); // [5, 50]

async fn fetch_all(urls: [string]): [string] {
let bodies: [string] = [];
for url in urls {
// Sequential awaits inside for — each iteration suspends
let body = await fetch(url);
bodies.push(body);
}
return bodies;
}

async fn fetch_all_concurrent(urls: [string]): [string] {
let handles = [];
for url in urls { handles.push(spawn fetch(url)); } // spawn inside for
let bodies: [string] = [];
for h in handles { bodies.push(await h); }
return bodies;
}

// Region per iteration — bulk-free scratch buffer
fn batched_process(items: [i32]) {
for item in items {
region scratch {
unsafe {
let buf: *mut u8 = alloc<u8>(256);
for i in 0..256 { buf[i] = (item + i) as u8; }
// bulk-free at end of iteration
}
}
}
}

Restrictions / Errors

KindTriggerDiagnosticHelp
Lexicallet in = 1unexpected token 'in', expected identifierTokenKind::In reserved
Parsefor x { } (missing in)expected 'in' or 'of' after for bindingWrite for x in xs { }
Parsefor in xs { } (missing binding)expected identifier after 'for'Provide for item in xs
Parsefor x in { } with no iterableexpected expression after 'in'Provide iterable/range
Typefor x in 42 (non-iterable)type 'i32' is not iterableUse array/range/map/string/iterator
Typex in 42 (membership on non-collection)type 'i32' has no 'contains' protocolCheck RHS is collection/range/string/map
Destructurefor (a,b) in xs where xs element not 2-tuplecannot destructure <type> into 2 bindingsFix pattern arity
BorrowMutating collection size while for ... in ... iterator livecannot mutate collection while iteratingSnapshot or use index while
RegionCapturing region pointer from for body past regionallocation from region does not outlive regionCopy data out

Common pitfall — confusing in iteration with C-style index loop:

// Adesh: directly iterates elements
for v in [10, 20, 30] { print(v); } // 10,20,30
// Not: for (let i=0; i<n; i++) — use range loops instead:
for i in 0..3 { print(i); } // 0,1,2 — then xs[i] if index is needed

See Also

  • forfor loop header and desugaring (ForIn/ForOf)
  • of — value iteration for ... of ... (TokenKind::Of)
  • while / do — condition-based loops
  • break / continue — loop control
  • range syntax0..N / 0..=N optimized counters
  • Control Flow — iterator protocol and loop lowering
  • src/parsing/lexer.rs:942, als/src/hover.rs:348