Collections and strings
Goal: use
List/Map/Setand the functional layer, and understand the string rules (explicit units, interpolation, byte comparison).
List, Map, Set (the collections package, tier 0)
Section titled “List, Map, Set (the collections package, tier 0)”They are not builtin: you import them. List[T] is a contiguous array that grows (not a linked list):
use collections
var xs := List[int].new()xs.push(1); xs.push(2); xs.push(3)io.println("{{xs.len()}} items")
primeiro := xs.get(0) // Optional[int] (safe access; none out of bounds)@requires(i < xs.len())fn at_seguro(xs: List[int], i: usize) -> int { return xs.at(i) } // contracted 'at'Access is by method (xs.at(i)/xs.get(i)), not xs[i]: raw [i] is only for builtin []T/[N]T/[*]T.
get -> Optional (“maybe”), at -> T (“I guarantee it”, under contract).
Map[K, V] is a hash table; the key must be hashable, a property derived by reflection
(structs work without declaring anything):
m := Map[string, int].new()m.put("ana", 90)score := m.get("ana") // Optional[int]m.update_or_insert("ana", fn(cur) => cur.or(0) + 1) // entry API, anti double-lookupSet[T] is a hash set (add/contains/union/intersection). Specialized structures
(Deque, PriorityQueue, SortedMap, Trie, RingBuffer) live in the official containers lib
(tier 1), in chapter 12.
Functional layer
Section titled “Functional layer”map/filter/fold/collect via UFCS; collect materializes into the container you choose:
pares := xs.filter(fn(x) => x % 2 == 0)dobr := xs.map(fn(x) => x * 2)soma := xs.fold(0, fn(acc, x) => acc + x)
como_set: Set[int] = primos.collect() // same iterator, output container chosen by the typeStrings
Section titled “Strings”string is always valid UTF-8. It has three views (.bytes, .codepoints, .chars, the last of which is
graphemes), and s[i] is forbidden (the unit is ambiguous). You qualify it:
s := "café"b := s.bytes.len // number of bytes (the view's .len field)n := s.chars.len // number of graphemesc0 := s.char_at(0) // one graphemeview := s.chars[1..3] // '..' = O(1) view; ':' = O(n) copyInterpolation uses {{ }}, through the Display interface (interpolating a type without Display is a compile
error, none of that <Object@0x...>):
io.println("{{nome}} placed {{count}} orders, total {{total.fixed(2)}}")Comparison is byte-by-byte: ==/!= (byte equality), </> (lexicographic order, which feeds
a sort directly). Unicode semantics (normalization, collation) is a method, not an operator, and there is no
===:
a == b // bytes (the common, fast case)a.equals(b, .semantic) // Unicode normalization (pulls in 'unicode' tables, opt-in)Growing a string follows the usual mutability rule: var mutates, by doubling, with no separate
StringBuilder (use string.with_capacity(n) when you know the size):
var buf := string.with_capacity(256)buf.append("a"); buf.append("b")Building from bytes: string.from[u8](bytes) -> Result[string, error{InvalidUtf8}] (validates
at the boundary).