Chapter 14 14 min read

Iterators

stdlib::iter provides the Iterator trait, plugs Vector's VecIter into it, and ships a family of free-function combinators that transform, reduce, and construct Vector<T> values. The transformers are eager: each one walks the input once and returns a fresh, fully-materialized *Vector<U> rather than a lazy adapter.

Import

import stdlib::iter::*;

Public surface at a glance

Kind Item
trait Iterator (assoc. type Item, method next)
impl Iterator for VecIter<T>
transformers iter_map, iter_filter, iter_take, iter_skip, iter_take_while, iter_skip_while
reducers iter_fold, iter_count, iter_any, iter_all, iter_find
numeric reducers iter_sum_int, iter_max_int, iter_min_int
constructors iter_range, iter_range_step, iter_repeat

VecIter<T> itself is declared in the builtins (builtins::vector) along with Vector::iter; this module only adds the Iterator impl for it.

The Iterator trait

pub trait Iterator {
    type Item;
    fn next(self: *Self) -> ?Self::Item;
}

A type is an iterator if it can hand back the next element wrapped in an option — some(x) while elements remain, none() once exhausted. The trait has one associated type, Item, naming the element type.

The module supplies one implementation, for VecIter<T> (the cursor returned by Vector::iter):

impl<T> Iterator for VecIter<T> {
    type Item = T;
    fn next(self: *VecIter<T>) -> ?T {
        if self.idx >= self.vec.len { return none(); }
        let v: T = self.vec.data[self.idx];
        self.idx = self.idx + 1;
        return some(v);
    }
}

Because the impl lives in this module, importing stdlib::iter::* is what makes v.iter() usable through a trait bound such as fn f<I: Iterator>(it: *I).

VecIter<T>

pub struct VecIter<T> {
    vec: *Vector<T>,
    idx: i32,
}
Field Type Meaning
vec *Vector<T> borrowed vector being walked
idx i32 next index to yield

You obtain a *VecIter<T> from v.iter() and pull elements with .next() until it returns none():

import stdlib::iter::*;

fn main() -> i32 {
    let v: *Vector<i32> = Vector::new();
    defer v.free();
    v.push_all!(10, 20, 30);

    let it: *VecIter<i32> = v.iter();
    while true {
        match it.next() {
            some(x) => println!(x),
            none()  => break,
        }
    }
    return 0;
}

A generic function can accept any iterator via a trait bound:

import stdlib::iter::*;

fn count_all<I: Iterator>(it: *I) -> i32 {
    let mut n: i32 = 0;
    while true {
        match it.next() {
            some(_) => { n = n + 1; },
            none()  => break,
        }
    }
    return n;
}

fn main() -> i32 {
    let v: *Vector<i32> = iter_range(1, 5);   // [1,2,3,4]
    defer v.free();
    println!(count_all(v.iter()));            // 4
    return 0;
}

Transformers — return a new *Vector<U>

Each transformer allocates and returns a brand-new vector that the caller owns. Bind the result with let v* = ... for scope-end auto-drop, or with an explicit raw let v: *Vector<T> = ... when you intend to pass it onward to another helper (see the mistake callout below).

Function Signature Result
iter_map fn iter_map<T, U>(v: *Vector<T>, f: fn(T) -> U) -> *Vector<U> apply f to each element
iter_filter fn iter_filter<T>(v: *Vector<T>, p: fn(T) -> bool) -> *Vector<T> keep elements where p is true
iter_take fn iter_take<T>(v: *Vector<T>, n: i32) -> *Vector<T> first n (clamped to length, n<0→empty)
iter_skip fn iter_skip<T>(v: *Vector<T>, n: i32) -> *Vector<T> drop first n (n<0 treated as 0)
iter_take_while fn iter_take_while<T>(v: *Vector<T>, p: fn(T) -> bool) -> *Vector<T> prefix until p first returns false
iter_skip_while fn iter_skip_while<T>(v: *Vector<T>, p: fn(T) -> bool) -> *Vector<T> suffix from the first p-false element

iter_map, iter_filter

import stdlib::iter::*;

fn double(x: i32) -> i32 { return x * 2; }
fn keep_pos(x: i32) -> bool { return x > 0; }

fn main() -> i32 {
    let v: *Vector<i32> = iter_range(0 - 3, 5);     // [-3,-2,-1,0,1,2,3,4]
    let m: *Vector<i32> = iter_map(v, double);
    let f: *Vector<i32> = iter_filter(m, keep_pos); // [2,4,6,8]
    let total: i32 = iter_sum_int(f);               // 20
    println!(total);
    return 0;
}

iter_map can change the element type (TU); iter_filter always preserves it. Neither mutates the input — the source vector is untouched.

iter_take, iter_skip, iter_take_while, iter_skip_while

import stdlib::iter::*;

fn lt3(x: i32) -> bool { return x < 3; }

fn main() -> i32 {
    let v: *Vector<i32> = iter_range(0, 6);   // [0,1,2,3,4,5]
    let a* = iter_take(v, 3);                 // [0,1,2]
    let b* = iter_skip(v, 2);                 // [2,3,4,5]
    let c* = iter_take_while(v, lt3);         // [0,1,2]
    let d* = iter_skip_while(v, lt3);         // [3,4,5]
    println!(a.len(), b.len(), c.len(), d.len());
    return 0;
}

take_while stops at the first element failing p; skip_while discards the leading run of p-true elements and keeps everything from the first p-false element onward (including later elements that happen to satisfy p).

Boundary behavior

Call Input [0,1,2,3] Result
iter_take(v, 99) oversized n clamped → [0,1,2,3]
iter_take(v, 0 - 5) negative n []
iter_skip(v, 0 - 5) negative n treated as 0[0,1,2,3]
iter_take_while(v, p) p true for none []
iter_skip_while(v, p) p true for all []
import stdlib::iter::*;

fn main() -> i32 {
    let v: *Vector<i32> = iter_range(0, 4);   // [0,1,2,3]
    let big* = iter_take(v, 99);              // [0,1,2,3]
    let neg* = iter_take(v, 0 - 5);           // []
    let sk*  = iter_skip(v, 0 - 5);           // [0,1,2,3]
    println!(big.len(), neg.len(), sk.len()); // 4 0 4
    v.free();
    return 0;
}

Chaining transformers

Transformers consume a *Vector and return a fresh one, so they chain by feeding each result into the next. When a result is consumed by another helper, bind it as a plain raw pointer (let x: *Vector<i32> = ...), then free each intermediate yourself:

import stdlib::iter::*;

fn double(x: i32) -> i32 { return x * 2; }
fn is_even(x: i32) -> bool { return x % 2 == 0; }

fn main() -> i32 {
    let v: *Vector<i32> = iter_range(1, 7);            // [1,2,3,4,5,6]
    let evens: *Vector<i32> = iter_filter(v, is_even); // [2,4,6]
    let dbl: *Vector<i32> = iter_map(evens, double);   // [4,8,12]
    let total: i32 = iter_sum_int(dbl);                // 24
    println!(total);
    v.free();
    evens.free();
    dbl.free();
    return 0;
}

Reducers — return a value

Reducers read the vector and collapse it to a single result. iter_find returns an option; the rest return plain values. None of them free or mutate the input.

Function Signature Result
iter_fold fn iter_fold<T, U>(v: *Vector<T>, init: U, f: fn(U, T) -> U) -> U left fold; f(acc, item) → new acc
iter_count fn iter_count<T>(v: *Vector<T>) -> i32 element count (same as v.len())
iter_any fn iter_any<T>(v: *Vector<T>, p: fn(T) -> bool) -> bool true if any matches (short-circuits)
iter_all fn iter_all<T>(v: *Vector<T>, p: fn(T) -> bool) -> bool true if all match; empty → true
iter_find fn iter_find<T>(v: *Vector<T>, p: fn(T) -> bool) -> ?T first match as some(x), else none()
import stdlib::iter::*;

fn add(acc: i32, x: i32) -> i32 { return acc + x; }
fn is_even(x: i32) -> bool { return x % 2 == 0; }
fn is_neg(x: i32) -> bool { return x < 0; }
fn is_pos(x: i32) -> bool { return x > 0; }

fn main() -> i32 {
    let v: *Vector<i32> = iter_range(1, 5);   // [1,2,3,4]
    let sum: i32 = iter_fold(v, 0, add);      // 10
    let n: i32 = iter_count(v);               // 4
    let anyNeg: bool = iter_any(v, is_neg);   // false
    let allPos: bool = iter_all(v, is_pos);   // true
    println!(sum, n, anyNeg, allPos);

    match iter_find(v, is_even) {
        some(x) => println!(x),               // 2
        none()  => {},
    }
    return 0;
}

iter_fold — the general reducer

fold is the most general reducer: the accumulator type U is independent of the element type T, so it can build a value of any shape. Here it folds a Vector<i32> into a comma-separated string:

import stdlib::iter::*;

fn join_csv(acc: string, x: i32) -> string {
    if acc.len() == 0 { return format!("{}", x); }
    return acc.concat(",").concat(format!("{}", x));
}

fn main() -> i32 {
    let v: *Vector<i32> = iter_range(1, 5);   // [1,2,3,4]
    defer v.free();
    let s: string = iter_fold(v, "", join_csv);
    println!(s);                              // 1,2,3,4
    return 0;
}

iter_any/iter_all short-circuit (stop at the first hit / first miss); iter_find returns the first matching element wrapped in ?T. iter_fold always visits every element.

Type-specific numeric reducers

Glide's generics do not constrain primitives to an Ord/numeric trait, so summing and finding extremes are provided as concrete i32 functions.

Function Signature Result
iter_sum_int fn iter_sum_int(v: *Vector<i32>) -> i32 sum of elements (0 for empty)
iter_max_int fn iter_max_int(v: *Vector<i32>) -> ?i32 largest element, none() if empty
iter_min_int fn iter_min_int(v: *Vector<i32>) -> ?i32 smallest element, none() if empty
import stdlib::iter::*;

fn main() -> i32 {
    let v: *Vector<i32> = Vector::new();
    defer v.free();
    v.push_all!(3, 7, 2, 9, 4);

    match iter_max_int(v) {
        some(m) => println!("max", m),   // 9
        none()  => {},
    }
    match iter_min_int(v) {
        some(m) => println!("min", m),   // 2
        none()  => {},
    }
    println!("sum", iter_sum_int(v));    // 25
    return 0;
}

iter_max_int / iter_min_int return ?i32 precisely so the empty case has a representable answer; unwrap with match, .has/.val, or ??.

Constructors

Function Signature Result
iter_range fn iter_range(start: i32, end: i32) -> *Vector<i32> [start, end); empty if end <= start
iter_range_step fn iter_range_step(start: i32, end: i32, step: i32) -> *Vector<i32> start, start+step, … < end; empty if step <= 0
iter_repeat fn iter_repeat<T>(value: T, n: i32) -> *Vector<T> n copies of value; empty if n <= 0
import stdlib::iter::*;

fn main() -> i32 {
    let r* = iter_range(0, 5);            // [0,1,2,3,4]
    let s* = iter_range_step(0, 10, 2);   // [0,2,4,6,8]
    let z* = iter_repeat(0, 4);           // [0,0,0,0]
    let t* = iter_repeat("x", 3);         // ["x","x","x"]
    println!(r.len(), s.len(), z.len(), t.len());
    return 0;
}

iter_range is the workhorse for building test data and is the canonical input to the transformers and reducers above. iter_range_step is half-open like iter_range and guards against non-positive steps (which would otherwise loop forever) by returning an empty vector.

Constructor edge cases

import stdlib::iter::*;

fn main() -> i32 {
    let a* = iter_range_step(0, 10, 3);    // [0,3,6,9]
    let b* = iter_range_step(10, 0, 1);    // []   (end <= start)
    let c* = iter_range_step(0, 5, 0);     // []   (step <= 0)
    let d* = iter_range_step(0, 5, 0 - 2); // []   (step < 0)
    println!(a.len(), b.len(), c.len(), d.len());  // 4 0 0 0

    let r* = iter_repeat(7, 3);            // [7,7,7]
    let z* = iter_repeat(7, 0);            // []
    let n* = iter_repeat(7, 0 - 4);        // []   (n <= 0)
    println!(r.len(), z.len(), n.len());   // 3 0 0
    return 0;
}

Method-form cross-reference

Every transformer/reducer that takes a single *Vector<T> as its first argument has an equivalent method on Vector<T> (defined in builtins::vector). The free function and the method do the same thing — pick whichever reads better.

Free function Vector method Notes
iter_map(v, f) v.map(f) element type may change
iter_filter(v, p) v.filter(p)
iter_take(v, n) v.take(n)
iter_skip(v, n) v.skip(n)
iter_take_while(v, p) v.take_while(p)
iter_skip_while(v, p) v.skip_while(p)
iter_fold(v, init, f) v.fold(init, f)
iter_count(v) v.len() iter_count is just v.len()
iter_any(v, p) v.any(p)
iter_all(v, p) v.all(p)
iter_find(v, p) v.find(p) returns ?T
iter_sum_int(v) v.sum() method form on Vector<i32>/Vector<f64>
iter_max_int(v) v.max() ?T
iter_min_int(v) v.min() ?T

iter_range/iter_range_step/iter_repeat have no method form — they are constructors, not operations on an existing vector.

import stdlib::iter::*;

fn double(x: i32) -> i32 { return x * 2; }
fn is_even(x: i32) -> bool { return x % 2 == 0; }
fn add(acc: i32, x: i32) -> i32 { return acc + x; }
fn is_pos(x: i32) -> bool { return x > 0; }
fn is_neg(x: i32) -> bool { return x < 0; }

fn main() -> i32 {
    let v: *Vector<i32> = Vector::new();
    defer v.free();
    v.push_all!(1, 2, 3, 4, 5);

    let m* = v.map(double);          // [2,4,6,8,10]
    let f* = v.filter(is_even);      // [2,4]
    let h* = v.take(3);              // [1,2,3]
    let t* = v.skip(2);              // [3,4,5]
    println!(m.len(), f.len(), h.len(), t.len());

    println!(v.fold(0, add));        // 15
    println!(v.any(is_neg));         // false
    println!(v.all(is_pos));         // true
    println!(v.sum());               // 15

    match v.max() {
        some(x) => println!("max", x),         // 5
        none()  => {},
    }
    match v.find(is_even) {
        some(x) => println!("first even", x),  // 2
        none()  => {},
    }
    return 0;
}

Ownership recap

  • Transformers and constructors allocate a new *Vector the caller owns.
  • Bind with let v* = ... to auto-free at scope end, or with a raw let v: *Vector<T> = ... when you will hand it to another helper.

  • Reducers borrow their input and free nothing; the input vector remains valid.
  • Vector::new() produces a raw *Vector; pair it with defer v.free() (and
  • use push_all!, which works on raw vectors) when you manage it manually.

  • v.iter() allocates a *VecIter that borrows v; keep v alive while
  • iterating and don't resize it mid-walk.