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

Collections reference

The container types: List (built in), std::hash_map::HashMap, std::hash_set::HashSet, std::range::Range, and the std::iterator protocol underneath for.

List<T>

Built in, with literal syntax: [1, 2, 3]. An empty literal needs a type annotation (let xs: List<str> = [];).

impl List<type T> {
	fun new(): List<T>
	fun push(&mut self, own item: T)
	fun pop(&mut self): Option<T>
	fun insert(&mut self, index: usize, value: T) // panics out of bounds
	fun remove(&mut self, index: usize): T        // panics out of bounds
	fun len(self): usize
	fun is_empty(self): bool
	fun iter(self): ListIterator<T>              // the lazy cursor; see Iterator
	fun get(self, index: usize): Option<T>
	fun first(self): Option<T>
	fun last(self): Option<T>
	fun map<U>(self, fn: |T| U): List<U>
	fun filter(self, predicate: |T| bool): List<T>
	fun filter_map<U>(self, fn: |T| Option<U>): List<U>   // both, one pass
	fun find(self, predicate: |T| bool): Option<T>
	fun fold<B>(self, init: B, fn: |B, T| B): B
	fun for_each(self, fn: |T| void)
	fun reverse(self): List<T>
	fun sort_by(own self, compare: |T, T| Ordering): List<T>   // stable
	fun to_array<const N>(self): Option<[T; N]>   // None unless len() == N
}
impl List<type T: Add + Default> { fun sum(self): T }
impl List<type T: Mul + Default> { fun product(self): T }
impl List<type T: Ord> { fun sort(self): List<T> }        // stable
impl List<type T: PartialEq> {
	fun contains(self, value: T): bool
	fun index_of(self, value: T): Option<usize>
}
impl List<type T: PartialEq> with PartialEq {
	fun eq(self, b: List<T>): bool           // element-wise, length first
}
impl List<type T: Display> { fun join(self, separator: str): str }
impl List<type T> with Default { fun default(): List<T> }       // []

Indexing is list[i]; iterate with for item in list (copies) or for e in &mut list (in-place views; see the memory model). list.iter() hands back a ListIterator<T> — a cursor over a snapshot of the list, and the entry to the adapter chain. The snapshot is rule 1 at work (the cursor stores the list in a slot that outlives the call, so it copies), which means a push after iter() is not walked, and that iter() itself costs a copy:

fun main() {
	mut live = [1, 2];
	mut cursor = live.iter();
	live.push(3);
	mut total = 0;
	for value in cursor {
		total += value;   // 1 + 2 — the snapshot predates the push
	}
	print(total);
}

The methods that take self by value are pure — they return a new list and leave the receiver alone. The mutating ones take &mut self: push, push_many, pop, insert, remove.

“Leave the receiver alone” reaches the ELEMENTS, not just the spine: the list a pure method returns never shares element storage with the receiver, so writing through xs.map(f)[0] cannot show up in xs[0]. That is rule 1 (a value is copied when it is stored), and it is why push and sort_by are written own — they keep what they are given. A fresh value costs nothing there: own copies a place, and a value with no other owner moves in.

fun main() {
	let words = ["alpha", "beta", "gamma"];
	let lengths = words.map(|word| word.len());
	print(lengths.sum());
	print(words.fold("", |joined, word| joined + word));
}

Searching & equality

find takes a predicate and short-circuits; contains and index_of take a value and need T: PartialEq. Missing answers are None, never a panic.

A List<T: PartialEq> is itself PartialEq: == compares element-wise, length first. Because the impl is conditional, lists nest (List<List<i32>> compares through its elements) and a struct holding a List field can [derive(PartialEq)].

fun main() {
	let scores = [40, 91, 65];
	print(scores.find(|n| n > 50).unwrap_or(0));  // 91
	print(scores.contains(65));                   // true
	print(scores.index_of(65).unwrap_or(0));      // 2
	print(scores.index_of(7).is_none());          // true
	print(scores == [40, 91, 65]);                // true
	print(scores == [40, 91]);                    // false — length first
}

Ordering

sort orders by Ord; sort_by takes a comparator returning Ordering. Both are stable — elements the comparator calls Equal keep their input order — and both return a new list, as does reverse.

import std::{ io::print, compare::Ordering };

fun main() {
	let ns = [10, 2, 1];
	print(ns.sort()[0]);      // 1
	print(ns.reverse()[0]);   // 1
	print(ns.sort_by(|a, b| b.compare(a))[0]);   // 10 — descending
}

Splicing

insert shifts the tail right (an index equal to len() appends); remove shifts it left and returns the element. Both panic on an out-of-range index, exactly as list[i] does — a bad index is a caller bug. Reach for get when you don’t know whether an index is live.

fun main() {
	mut xs = [1, 2, 4];
	xs.insert(2, 3);
	print(xs.len());       // 4
	print(xs.remove(0));   // 1
	print(xs[0]);          // 2
}

Appending a run

push_many(items) appends every item of a run, in order: another list, a fixed array [T; n], a HashSet (its members in insertion order) or any iterator — whatever is Items<T> (std::iterator). The run is taken whole first, so a list pushed onto itself appends a copy of itself as it stood.

fun main() {
	mut xs = [1, 2];
	xs.push_many([3, 4]);
	xs.push_many([5, 6].iter().map(|x| x * 10));
	xs.push_many(xs);
	print(xs.len());   // 12
}

A reactive list takes the same call as ONE write: ListCell::push_many is a single Splice at the end, so its readers see one change for the batch (std::reactive).

Joining

join renders each element through Display and puts separator between them. It lives in std::display beside the bound it needs, so it is the one List method that takes an import — and calling it without one names the import in the error.

import std::{ io::print, display::Display };

fun main() {
	print(["alpha", "beta", "gamma"].join(", "));  // alpha, beta, gamma
	print([1, 2, 3].join("-"));                    // 1-2-3
}

Fixed arrays [T; n]

A fixed array (the tour introduces it) is a value whose length is part of its type. std implements its traits once for every length, so [u8; 4] and [str; 100] get the same surface:

  • == / != when the element is PartialEq — equal when every position is. Two arrays of different lengths are different types, so == never meets them; a [derive(PartialEq)] struct with an array field derives. The array is Eq when the element is, so a T: Eq takes an [i32; 4].
  • Hashable when the element is — an array keys a HashMap or joins a HashSet.
  • Debug when the element is — T: Debug takes an array, and it prints as a List does.
  • Default when the element is — the element’s default at every position, so a [derive(Default)] struct with an array field derives. The array type heads the path: [i32; 4]::default() (default_of<[i32; 4]>() in std::default is the same call spelled generically).
  • Json/FromJson when the element is — a JSON array, decoded only at its own length (see encoding).
  • Items<T> — xs.push_many(fixed) appends one.
  • to_list(), a List copy of the elements, and iter(), a cursor over a snapshot of them that opens the adapter chain. An array never coerces to a List; these are the conversions.
  • The way back is the list’s: list.to_array() answers Option<[T; N]>, a copy when the list holds exactly N elements and None otherwise. N comes from the type the result lands in or is written, xs.to_array<3>().
import std::{ hash_map::HashMap, iterator::Iterator };

fun main() {
	let rgb: [u8; 3] = [255, 128, 0];
	print(rgb == [255, 128, 0]);             // true

	mut names: HashMap<[u8; 3], str> = HashMap::new();
	names.insert(rgb, "orange");
	print(names.get([255, 128, 0]).unwrap_or("?"));  // orange

	mut xs = [1];
	let more: [i32; 2] = [2, 3];
	xs.push_many(more);
	print(xs.len());                           // 3
	let doubled = rgb.iter().map(|c| c.as_i32() * 2).to_list();
	print(doubled[0]);                         // 510
	print(rgb.to_list().len());                // 3

	let back: Option<[i32; 3]> = xs.to_array();
	print(back.unwrap()[2]);                   // 3
	print(xs.to_array<2>().is_none());         // true
}

The same door is open to your own traits: an impl head may bind the length (impl [type T; _] with Count, or impl [type T; const N] to read it as N), and a function may take one (fun total<const N>(values: [i32; N])). The types chapter states the rules.

HashMap<K, V>

impl HashMap<type K: Hashable, type V> {
	fun new(): HashMap<K, V>
	fun insert(&mut self, key: K, value: V)
	fun get(self, key: K): Option<V>
	fun get_or_insert(&mut self, key: K, make: || V): V   // the held value, or make() stored and answered
	fun contains_key(self, key: K): bool
	fun remove(&mut self, key: K)
	fun len(self): usize
	fun is_empty(self): bool
	fun keys(self): List<K>
	fun values(self): List<V>
	fun entries(self): List<(K, V)>
}
impl HashMap<type K: Hashable, type V: PartialEq> {
	fun contains_value(self, value: V): bool
}
impl HashMap<type K: Hashable, type V: PartialEq> with PartialEq   // same keys, equal values; order ignored
impl List<(type K: Hashable, type V)> { fun to_map(self): HashMap<K, V> }
impl HashMap<type K: Hashable, type V> with Default {
	fun default(): HashMap<K, V>                 // the empty map
}

A HashMap iterates in insertion order, on both backends: keys(), values() and entries() walk the keys in the order they first arrived, an overwrite keeps its key’s place, and a key removed and inserted again goes to the end. The name says how it keys — by Hashable value — not that its order is arbitrary. Equality follows the same line: two maps are == when they hold the same keys with equal values, whatever order either was built in, so a struct holding a map can [derive(PartialEq)]. Before v0.42 the type was std::map::Map; that name had one release as a deprecated alias and is gone since v0.44, and a use of it is refused with the new spelling.

Keys compare by value. Scalars work directly, and so does a backed enum — one with explicit backing values, which is that value at runtime, so the backing value is the key and no derive is needed:

import std::hash_map::HashMap;

enum Align { Start = "flex-start", End = "flex-end" }

fun main() {
	mut widths: HashMap<Align, i32> = HashMap::new();
	widths.insert(Align::Start, 1);
	print(widths.get(Align::Start).unwrap_or(0)); // 1
}

An unbacked enum (enum Plain { A, B }) is not a key on its own: without a backing value it lowers to an array, like a struct, so it needs the derive along with every other aggregate. A tuple of Hashable elements is a key as it stands (HashMap<(i32, str), V>). A struct, an unbacked or payload-carrying enum, or a List key works as long as it is Hashable. Derive it:

import std::hash_map::HashMap;
import std::hash::Hashable;
import std::option::Option::{ self, Some, None };

[derive(Hashable)]
struct Point {
	x: i32,
	y: i32,
}

fun main() {
	mut seen: HashMap<Point, str> = HashMap::new();
	seen.insert(Point { x = 1, y = 2 }, "origin-ish");
	// A fresh, distinct Point with equal fields hits.
	match seen.get(Point { x = 1, y = 2 }) {
		Some(let label) => print(label), // origin-ish
		None => print("miss"),
	}
}

A generic struct derives too: the derive binds each parameter a field reaches under Hashable, so struct Tagged<T> { item: T, tags: List<T> } is a key at Tagged<str>, and an argument that is not Hashable is refused where it is used.

keys() returns the real Ks (in insertion order), and the key is snapshot on insert, so mutating the original afterward can’t desync the map.

entries() pairs keys()/values() into one List<(K, V)> snapshot, so walking both together needs no hand-zipping; contains_value (needing V: PartialEq, unlike the rest of HashMap) is the value-side counterpart to contains_key:

import std::hash_map::HashMap;

fun main() {
	mut scores: HashMap<str, i32> = HashMap::new();
	scores.insert("alice", 1);
	scores.insert("bob", 2);
	mut total = 0;
	for entry in scores.entries() {
		total = total + entry.1;   // entry.0 is the key, entry.1 the value
	}
	print(total);                        // 3
	print(scores.contains_value(2));     // true
	print(scores.contains_value(9));     // false
}

get_or_insert is the entry idiom: the value held for key, or — on a miss, and only there — the one make builds, inserted and then answered. The maker runs once per key, at the call site:

import std::hash_map::HashMap;

fun main() {
	mut lengths: HashMap<str, usize> = HashMap::new();
	print(lengths.get_or_insert("alpha", || "alpha".len()));  // 5 -- made and stored
	print(lengths.get_or_insert("alpha", || 0));              // 5 -- held; the maker did not run
	print(lengths.len());                                     // 1
}

HashSet<T>

impl HashSet<type T: Hashable> {
	fun new(): HashSet<T>
	fun insert(&mut self, value: T)
	fun contains(self, value: T): bool
	fun remove(&mut self, value: T)
	fun len(self): usize
	fun is_empty(self): bool
	fun values(self): List<T>
	fun union(self, other: HashSet<T>): HashSet<T>
	fun intersection(self, other: HashSet<T>): HashSet<T>
	fun difference(self, other: HashSet<T>): HashSet<T>
}
impl List<type T: Hashable> { fun to_set(self): HashSet<T> }
impl HashSet<type T: Hashable> with Default { fun default(): HashSet<T> }  // the empty set
impl HashSet<type T: Hashable> with PartialEq                             // same members; order ignored
impl HashSet<type T: Hashable + Wire> with Wire                          // the list of its members

Value-keyed like HashMap (element T must be Hashable); for x in set iterates the elements in insertion order (a duplicate insert keeps its value’s place; a value removed and inserted again goes to the end). std::set::Set was its name before v0.42, removed in v0.44 as Map was.

A set of Wire members is Wire: it crosses as the list of its members, in insertion order, so a struct with a set field derives Wire (and a mirrored store carries one, each member a boundary of its own — contains(x)).

union/intersection/difference are the standard set operations, each returning a new HashSet and leaving both receivers untouched:

import std::hash_set::HashSet;

fun main() {
	mut a: HashSet<i32> = HashSet::new();
	a.insert(1);
	a.insert(2);
	a.insert(3);
	mut b: HashSet<i32> = HashSet::new();
	b.insert(2);
	b.insert(3);
	b.insert(4);
	print(a.union(b).len());          // 4 -- {1, 2, 3, 4}
	print(a.intersection(b).len());   // 2 -- {2, 3}
	print(a.difference(b).len());     // 1 -- {1}
}

Memo<K, V>

impl Memo<type K: Hashable, type V> {
	fun new(): Memo<K, V>
	fun get_or_insert(self, key: K, make: || V): V
	fun get(self, key: K): Option<V>
	fun forget(self, key: K)
	fun clear(self)
	fun len(self): usize
}

A per-key cache of values made on first ask. get_or_insert answers what is held for key, or runs make, inserts the result and answers that. get is the passive read — it makes nothing. (get_or is the old name, kept one release as a deprecated alias.)

import std::memo::Memo;

fun main() {
	let squares: Memo<i32, i32> = Memo::new();
	print(squares.get_or_insert(7, || 7 * 7));   // 49 -- made
	print(squares.get_or_insert(7, || 0));       // 49 -- held; the maker did not run
	squares.forget(7);
	print(squares.len());                        // 0
}

make runs at the call site, so a maker that reads an ambient context — the rpc client, a theme — reads the caller’s. Its lifetime is not the caller’s, though: what the maker builds outlives the caller. The memo keeps it for the life of the program, past every owner the first call ran under, so a derivation built in a maker is .cell_global(), and a lease (.cell(), effect) belongs at the call site, on what the memo answers:

// The memo holds the program-lifetime cell of the handle; the view that asks
// takes its own lease, released with the view.
let bodies: Memo<str, SignalCell<Option<RemoteSource<MessageBody>>>> = Memo::new();

fun body_of(id: str): MemoCell<Option<MessageBody>> {
	bodies
		.get_or_insert(id, || client_cell().derive(|client| client.map(|c| c.get_message(id))).cell_global())
		.and_then(|mirror| mirror)
		.memo()
}

A .cell() inside the maker would be tied to the FIRST caller’s owner: it dies with that view, and every later ask is answered with the dead cell — stale, and never updating again. The compiler warns at a .cell() or an effect written directly in a maker.

You do not need a memo to get one remote handle per id: a generated handle stub already answers the same mirror for the same method and arguments (see the services guide). Nothing is evicted by itself; forget is yours, for a value that turned out wrong.

It holds a Shared table inside, so a Memo bound with let at module level is written by every call site that reads it; no mut is needed.

The one-slot twin is Shared<Option<T>>::get_or_insert(make): the held value, or make() stored and answered — the same rule about what make builds. The unshared form is HashMap::get_or_insert(key, make) on a map you hold mut.

Hashable

A key’s value is turned into a Hash (a canonical key) by key.hash(). Three routes reach it:

  • Scalars (str, bool, every sized numeric), a fixed array [T; n] and List/Option of a Hashable — implemented by std.
  • A backed enum — implemented by the compiler beside its value()/parse() (see the types chapter), off the same opt-in: writing = "flex-start" is what makes the enum that value, and the value is the key. So Align::Start.hash() and Align::Start.value().hash() are the same key, and a backed enum is accepted as a field of a derived type too. A resource enum is excluded — a resource cannot be hashed by value.
  • [derive(Hashable)] — for a struct or an enum whose fields are all Hashable (scalars, str, bool, List/Option of Hashable, a backed enum, or another derived type); a closure, HashSet, HashMap, or Shared field is rejected. Writing it on a backed enum is harmless and does nothing.

You can also hand-write impl Hashable to key by a subset of fields — except on a backed enum, whose impl the compiler already provides — and build your own container by bounding on K: Hashable and keying a HashMap<Hash, …> yourself.

A Hash is opaque: you can hold it, compare two with ==, hash it again (it is itself Hashable, being already a canonical key), and use it as a key. You cannot read the value inside, which is what keeps the representation free to change. Equal values hash equal, so == on two hashes answers “same key?”:

import std::hash::Hashable;

[derive(Hashable)]
struct Point {
	x: i32,
	y: i32,
}

fun main() {
	let here = Point { x = 1, y = 2 };
	print(here.hash() == Point { x = 1, y = 2 }.hash()); // true
	print(here.hash() == Point { x = 9, y = 2 }.hash()); // false
}

Equality is the whole of a Hash’s operator surface — there is no order on a canonical key, so < is a compile error rather than a lexicographic compare of the underlying JSON.

One corner: a float inside an aggregate key canonicalizes through JSON, where NaN becomes null and -0/+0 collapse to 0, so those collide. Bare numeric keys don’t have this (they key by JS value directly).

Range

End-exclusive integer ranges, made for for:

Range::new(start: i32, end: i32): Range   // start..end, end excluded
range.next(&mut self): Option<i32>
import std::range::Range;

fun main() {
	mut total = 0;
	for i in Range::new(1, 5) {   // 1, 2, 3, 4
		total += i;
	}
	print(total);
}

Iterator

The protocol for consumes, and the seam for custom sequences:

trait Iterator<T> { fun next(&mut self): Option<T>; }
Iterator::from_fn(fn: || Option<T>): IteratorFromFn<T>   // an iterator from a closure

next takes &mut self because advancing is a mutation of the iterator’s own state — a cursor, a counter, a running total. Implement it on a struct of yours and the type works in a for loop, and satisfies an I: Iterator<T> bound:

import std::iterator::Iterator;
import std::option::Option::{ self, Some, None };

struct Countdown {
	remaining: i32,
}

impl Countdown with Iterator<i32> {
	fun next(&mut self): Option<i32> {
		if self.remaining <= 0 {
			None
		} else {
			self.remaining -= 1;
			Some(self.remaining)
		}
	}
}

fun main() {
	mut countdown = Countdown { remaining = 3 };
	for n in countdown {
		print(n);   // 2, 1, 0
	}
}

Range implements Iterator<i32>, and so does every adapter below.

One thing to know about the loop: for resolves the protocol on the method name, so a type with a next(&mut self): Option<T> drives a loop whether or not it declares the trait. Declaring it is what buys the adapters below — and what lets a generic bound accept your type.

That resolution is ordinary method resolution, which means an inherited default counts. If the trait supplies a next body and your impl block is empty, your type still iterates — the same next you would reach by writing value.next():

import std::option::Option::{ self, Some, None };

trait Countdown<T> {
	fun tick(&mut self): Option<T>;
	fun next(&mut self): Option<T> { self.tick() }   // inherited by every impl
}

struct Down { at: i32 }

impl Down with Countdown<i32> {
	fun tick(&mut self): Option<i32> {
		if self.at <= 0 { None } else { self.at -= 1; Some(self.at) }
	}
}

fun main() {
	mut down = Down { at = 3 };
	for n in down {
		print(n);   // 2, 1, 0
	}
}

Precedence is the usual one: a next declared on the type itself beats an inherited one, and two different traits offering same-named defaults is an ambiguity the loop reports rather than resolving for you — a for has no way to name a provider, so declare next on the type to settle it.

The name is what resolves, but the shape is still checked: the method has to return an Option, because the loop stops at None. A next that returns anything else is a compile error rather than a loop that quietly runs zero times — or, worse, throws. The return annotation is optional and the rule does not care: annotate it and the annotation is checked, leave it off and the body is, so fun next(&mut self) { (self.fn)() } is fine (it yields an Option) and fun next(&mut self) { self.count += 1; } is not (it yields nothing).

What a for can iterate

Exactly two things:

  • A type with next (or next_mut, for for e in &mut it) — the protocol above, declared on the type or inherited from a trait default.
  • A natively iterable value: List<T>, a fixed array [T; n], a tuple, a str (yielding its characters), HashSet<T> (insertion order), and any host type an external struct names.

Anything else is a compile error. A struct or enum of your own that provides no next is not iterable — it has no meaning to fall back on, since a struct is its fields and an enum is its variant tag at runtime:

struct Cursor { items: List<i32>, index: usize }

fun main() {
	mut walked = Cursor { items = [1, 2], index = 0 };
	for item in walked {   // cannot iterate `Cursor`: it has no `next`
		print(item);
	}
}

HashMap is in that group: walk it through entries(), keys() or values(), as the HashMap section above does.

Adapters

Every Iterator gets these, as trait defaults — implement next and you have all of them:

fun map<U>(self, fn: |T| U): Mapped<Self, T, U>
fun filter(self, predicate: |T| bool): Filtered<Self, T>
fun filter_map<U>(self, fn: |T| Option<U>): FilterMapped<Self, T, U>   // both, one pass
fun take(self, count: usize): Taken<Self, T>
fun skip(self, count: usize): Skipped<Self, T>
fun enumerate(self): Enumerated<Self, T>                       // (0, a), (1, b), …
fun zip<U, J: Iterator<U>>(self, other: J): Zipped<Self, J, T, U>
fun chain<J: Iterator<T>>(self, other: J): Chained<Self, J, T>

filter_map is map and filter at once: the projection answers Some(value) to keep that value and None to drop the element, so a partial or fallible projection needs neither a two-stage chain nor an Option to unwrap afterwards. List carries the eager twin under the same name.

They are lazy: each returns a small struct holding its upstream, and nothing runs until something pulls. So a chain makes one pass over the source and builds no intermediate lists.

fun main() {
	mut pipeline = [1, 2, 3, 4, 5, 6]
		.iter()
		.filter(|n| n % 2 == 0)
		.map(|n| n * 10)
		.take(2);
	for value in pipeline {
		print(value);   // 20, 40
	}
}

Laziness is what makes take more than a convenience: it never pulls past its budget, so it bounds a source that has no end.

import std::iterator::Iterator;
import std::option::Option::{ self, Some, None };

struct Naturals {
	at: i32,
}

impl Naturals with Iterator<i32> {
	fun next(&mut self): Option<i32> {
		self.at += 1;
		Some(self.at)
	}
}

fun main() {
	mut squares = Naturals { at = 0 }.map(|n| n * n).take(3);
	for value in squares {
		print(value);   // 1, 4, 9
	}
}

zip stops with the shorter side, and enumerate numbers what reaches it — put it after a filter and you get the positions in the output, not in the source.

The adapter types are named in the past participle — Mapped, Taken, Filtered — while the methods keep the plain names. That is deliberate: a verb’s bare name can be a std type too (the hash map was Map until v0.42), and vilan’s method resolution picks by registration order rather than reporting a collision, so the type names stay out of each other’s way.

A for binding gets the element type the iterator was instantiated at, whatever shape it is — a tuple, a struct, another container, a closure:

struct Point { x: i32, y: i32 }

fun main() {
	for pair in [(1, "a"), (2, "b")].iter() {
		print(pair.0);                          // 1, then 2
	}
	for point in [Point { x = 1, y = 2 }].iter() {
		print(point.x);                         // 1
	}
	for inner in [[1, 2, 3], [4]].iter() {
		print(inner.len());                     // 3, then 1
	}
}

Terminations

An adapter chain does nothing until it is terminated. These consume the iterator and hand back an ordinary value:

fun to_list(mut self): List<T>
fun fold<B>(mut self, init: B, fn: |B, T| B): B
fun for_each(mut self, fn: |T| void)
fun count(mut self): i32
fun any(mut self, predicate: |T| bool): bool     // short-circuits on the first hit
fun all(mut self, predicate: |T| bool): bool     // short-circuits on the first miss
fun rev(mut self): ListIterator<T>               // a BARRIER — see below

to_list is the primary one, and it is deliberately explicit. A method that names what it builds needs no type annotation, reads at the call site, and works in the middle of an expression — xs.iter().filter(f).to_list().len() — which is exactly where an inference-driven collect gives up. There is no collect in vilan, by design; if one is ever added it will sit beside this family, never replace it.

fun main() {
	let evens = [1, 2, 3, 4, 5, 6].iter().filter(|n| n % 2 == 0).to_list();
	print(evens.len());                                     // 3
	print([1, 2, 3].iter().fold(0, |total, n| total + n));   // 6
	print([1, 2, 3, 4].iter().filter(|n| n > 2).count());    // 2
	print([1, 2, 3].iter().any(|n| n == 2));                 // true
	print([1, 2, 3].iter().all(|n| n > 0));                  // true
}

any and all short-circuit, so they can answer over a source that has no end; count, fold, for_each and to_list pull everything, so bound such a source with take first.

rev is a barrier, not a lazy adapter: it drains its upstream into a List, reverses that, and hands back a ListIterator. So the chain continues, but the work up to that point has already happened — and rev never returns over an unbounded source. (A lazy reverse needs a double-ended protocol, where every adapter decides whether it can walk backwards. That is purely additive later: rev’s signature would not change, only its body.)

For a HashSet or a HashMap, terminate with to_list() and convert:

impl List<type T: Hashable>            { fun to_set(self): HashSet<T> }
impl List<(type K: Hashable, type V)>  { fun to_map(self): HashMap<K, V> }
import std::hash_map::HashMap;
import std::hash_set::HashSet;
import std::option::Option::{ self, Some, None };

fun main() {
	let unique = [1, 2, 2, 3].iter().filter(|n| n > 1).to_list().to_set();
	print(unique.len());   // 2

	let lengths = ["alpha", "hi"].iter().map(|word| (word, word.len())).to_list().to_map();
	print(lengths.get("hi").unwrap_or(0));    // 2
}

These two live on List rather than on Iterator, and the reason is worth knowing because it shapes what you can write yourself: to_set needs T: Hashable, Iterator<T> does not bound T, and a trait default may not require a bound its trait does not declare — nor can a method carry one of its own. So a to_set written as a trait default is rejected at its own definition, before any call. Putting it beside the bound it needs is the same choice join makes with Display. A repeated key in to_map keeps the last pair, matching insert.