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

Standard library

The library is ordinary borrow-core code in lib/, checked with your program. include name brings one module into scope, together with the modules it includes itself; only what a program calls is compiled. This page lists each module’s user-facing names, and the guide chapter linked from each section teaches them.

includeforbrings in
(none)the prelude
failerrors
arithEuclidean divisionfail
recrecursion handlersbuf
iothe I/O effects, decimal output
textiotext I/Otext, io
textstrings and text viewsbuf, slice, fail
bufgrowable buffers, a priority queue
slicearray views
keyequality, hashing, orderslice
maphash map and setkey, buf
lista generic listrec
yieldgenerators
adaptersgenerator adaptersyield
itersadapters over data
asynctasks and a schedulerlist
floatfloat operations, printing, parsingtextio

Reading the signatures

Signatures are written as they are declared. Two conventions recur.

Hand-back functions. A function that changes or reads a value without consuming it takes the value first and returns it first: push(b: buf A, x: A) -> buf A * 1. You call it with a lend, push(&mut b, x), which moves b in, rebinds it to what comes back, and answers the rest, here (). len_of(&b) reads one the same way and answers the i32. A lend must be the whole of a let or a statement: let n = len_of(&b);.

A function that returns the value alone, with nothing after it (truncate, clear, reserve, grow, heap_push, clear_str, and the view functions that hand back an array_mut), is rebound with let: let b = truncate(b, 1);, let v = set(v, 0, x);.

Sums. T + 1 is an optional T: inl x when there is one, inr () when not. bool is 1 + 1. An Option[T] matches as Some(x) or None.

The prelude

In scope in every program, with no include.

namesignature
true, falsebool
not!(bool => bool)negation
to_i32!(u8 => i32)a byte as 0…255
to_u8!(i32 => u8)the low byte of an i32
Option[T]data Option[T] = Some(T) | Nonean optional value
Vec[T]data Vec[T] = Vec(buf T)a growable vector; every buf function works on it

The built-in forms (alloc, free, array_new, checked_div, div_fuel, …) are in the Language reference.

fail

The Fail effect and its handlers. Errors teaches them.

namesignature
Fail[E]effect Fail[E] { ctl raise(e: E) -> never }an error of type E
raise(e)e: E, answers nevere to the nearest handler of Fail[E]
to_resulthandler to_result[E, A]: Fail[E], A -> A + Einl the value, or inr the error
default(d)handler default[E, A](d: A): Fail[E], A -> Athe value, or d
map_err(f)handler map_err[E, E2, A](f: !(E => E2)): Fail[E], A -> A with Fail[E2]an error e raised again as f(e)

arith

Euclidean division, and the names of division’s error. / and % raise Fail[DivError] with no include; include arith adds the rest. Division teaches them.

namesignature
DivErrortype DivError = 1 + 1division’s error
div_by_zero, div_overflowDivErrorinl () and inr (): a zero divisor, and MIN / -1
div_eucliddiv_euclid[T](a: T, b: T) -> Tthe q of a = b * q + r with 0 <= r < |b|; raises as / does
rem_euclidrem_euclid[T](a: T, b: T) -> Tthat r, never negative
include textio
include arith

-- The minute within its hour of `m` minutes after midnight, which may be negative.
fn minute(m: i32) -> i32 = rem_euclid(m, 60)

main -> 1 =
  print_dec(minute(0 - 75));
  print_byte(b' ');
  print_dec((0 - 75) % 60);
  print_byte(b' ');
  match checked_div(minute(0), 0) {
    inl q -> print_dec(q)
  | inr e -> match e { inl u -> print_lit("division by zero") | inr u -> print_lit("overflow") }
  };
  print_byte(b'\n')
-- output: 45 -15 division by zero

rec

The handlers a recursive function needs. A function that calls itself has a budget of 10000 levels and performs Exhausted when it runs out. Recursion teaches them.

namesignature
Exhaustedeffect Exhausted { ctl exhausted() -> never }the budget ran out
on_exhausted(d)handler on_exhausted[A](d: !(1 => A)): Exhausted, A -> Athe value, or d(())
exhausted_to_optionhandler exhausted_to_option[A]: Exhausted, A -> A + 1inl the value, or inr ()
deepwith deep { e }the recursions in e keep their pending calls on the heap: no depth limit
include textio
include rec

fn depth(n: i32) -> i32 = if n == 0 { 0 } else { 1 + depth(n - 1) }

main -> 1 =
  match with exhausted_to_option { depth(100) } {
    inl d -> print_dec(d)
  | inr u -> print_lit("too deep")
  };
  print_byte(b' ');
  print_dec(with deep { depth(100) });
  print_byte(b'\n')
-- output: 100 100

io

The system’s effects, and decimal output. main is given each effect by the platform; any other function performs them and its caller serves them. Input and output teaches them.

effectoperations
Stdoutprint_byte(b: u8), print_bytes(v: array_imm 'b u8)
Stderreprint_byte(b: u8), eprint_bytes(v: array_imm 'b u8)
Stdininput_byte() -> u8 + 1, input_into(v: array_mut 'b u8) -> array_mut 'b u8 * i32
Clocknow() -> i64 (nanoseconds, monotonic), sleep(ns: i64)
Argsarg_byte() -> u8 + 1: the arguments’ bytes, each argument followed by 0
namesignature
print_decprint_dec(x: i32) -> 1x in decimal to standard output
print_dec64print_dec64(x: i64) -> 1the same at i64
eprint_deceprint_dec(x: i32) -> 1x in decimal to standard error
ns_of_msns_of_ms(ms: i32) -> i64milliseconds as nanoseconds, for sleep

textio

Text over the standard streams. Input and output teaches them.

namesignature
print_litprint_lit(l: lit) -> 1a literal "…" to standard output, in one write
print_strprint_str<'a>(t: text 'a) -> 1a text view to standard output
print_stringprint_string(s: string) -> stringa string to standard output, and the string back
eprint_lit, eprint_stras print_lit, print_strto standard error
read_inputread_input() -> stringthe whole of standard input
next_argnext_arg(s: string) -> string * boolthe next argument appended to s, and whether there was one
include textio

main -> 1 =
  let s = read_input();
  let n = borrow imm s { t -> fold_lines(t, !{ (l, k) -> k + 1 }, 0) };
  let s = print_string(s);
  print_dec(n);
  print_lit(" lines\n")
-- stdin: alpha
-- stdin: beta
-- output: alpha
-- output: beta
-- output: 2 lines

text

string, a growable buffer of UTF-8 bytes, and text 'a, a view of some (text_mut 'a when it is exclusive). A view is lent from a string with borrow imm s { t -> … }, or from a literal with borrow imm "…" { t -> … }, and the functions that cut it answer sub-views without copying. A string is a buf of u8, so len_of, pop and the rest work on it. Buffers and strings teaches them.

Building a string. The push_ functions are called with &mut s.

namesignature
box string(cap)stringan empty string with room for cap bytes (string_new(cap))
push_litpush_lit(s: string, l: lit) -> string * 1a literal appended
push_strpush_str<'a>(s: string, t: text 'a) -> string * 1a view’s bytes appended
push_bytepush_byte(s: string, x: u8) -> string * 1a byte appended
push_charpush_char(s: string, c: i32) -> string * 1a character (a scalar value) appended as UTF-8
push_decpush_dec(s: string, x: i32) -> string * 1x in decimal appended
clear_strclear_str(s: string) -> stringemptied, the capacity kept
to_stringto_string<'a>(t: text 'a) -> stringa copy of a view
string_of_litstring_of_lit(l: lit) -> stringa copy of a literal

Reading text.

namesignature
eq_liteq_lit<'a>(t: text 'a, l: lit) -> boolwhether t has the literal’s bytes
lit_lenlit_len(l: lit) -> i32a literal’s length in bytes
count_charscount_chars<'a>(t: text 'a) -> i32the number of characters
char_atchar_at<'a>(t: text 'a, i: i32) -> i32 * i32the character at byte i, and its length in bytes
split_charsplit_char<'a>(t: text 'a) -> i32 * text 'a + 1the first character and the rest
split_oncesplit_once<'a>(t: text 'a, sep: u8) -> text 'a * text 'a + text 'athe views before and after the first sep, or inr t
next_linenext_line<'a>(t: text 'a) -> text 'a * text 'a + 1the first line and the rest
fold_linesfold_lines[S]<'a>(t: text 'a, f: !(text 'a * S => S), s: S) -> Seach line folded into s, in order
trimtrim<'a>(t: text 'a) -> text 'awithout leading and trailing white space
skip_while, rskip_whileskip_while<'a>(t: text 'a, p: !(u8 => bool)) -> text 'awithout the leading (trailing) bytes p holds of
prefix_len, suffix_lenprefix_len<'a>(t: text 'a, p: !(u8 => bool)) -> i32how many leading (trailing) bytes p holds of
is_spaceis_space(b: u8) -> boolspace, tab, carriage return or newline
find_strfind_str<'a, 'b>(t: text 'a, p: text 'b) -> i32 + 1the index of the first occurrence of p

UTF-8 and parsing.

namesignature
str_from_utf8str_from_utf8<'a>(v: array_imm 'a u8) -> text 'a with Fail[i32]the bytes as text; raises the index of the first bad byte
from_utf8from_utf8(b: buf u8) -> string with Fail[i32]a byte buffer as a string, in place; raises as above
utf8_errorutf8_error<'a>(t: text 'a) -> i32 + 1the index of the first bad byte, or inr ()
ParseErrortype ParseError = 1 + (u8 + 1)no digits, a byte that isn’t a digit, or too large
parse_i32parse_i32<'a>(t: text 'a) -> i32 with Fail[ParseError]a decimal integer, with an optional -
fold_i32sfold_i32s[S]<'a>(t: text 'a, f: !(i32 * S => S), s: S) -> Severy integer written in t, folded into s
fold_i64sas fold_i32s, at i64
add_checked, mul_checkedadd_checked(a: i32, b: i32) -> i32 with Fail[1]a + b (a * b); raises () on overflow

In place, and buffers of views.

namesignature
make_ascii_uppercasemake_ascii_uppercase<'a>(v: text_mut 'a) -> text_mut 'aa–z to A–Z
make_ascii_lowercasemake_ascii_lowercase<'a>(v: text_mut 'a) -> text_mut 'aA–Z to a–z
sort_viewssort_views<'a>(b: buf (text 'a)) -> buf (text 'a)a buffer of views sorted by their bytes, stably
view_atview_at<'a>(b: buf (text 'a), i: i32) -> buf (text 'a) * text 'aa copy of the view at i
swap_viewsswap_views<'a>(b: buf (text 'a), i: i32, j: i32) -> buf (text 'a)the views at i and j exchanged
include textio

-- The number after the `=` of `key = value`, or 0 when there is no `=`.
fn value<'t>(line: text 't) -> i32 with Fail[ParseError] =
  match split_once(line, b'=') {
    inl (k, v) -> parse_i32(trim(v))
  | inr l -> 0
  }

main -> 1 =
  let mut s = box string(16);
  push_lit(&mut s, "caf");
  push_char(&mut s, 0xe9);
  push_lit(&mut s, " = ");
  push_dec(&mut s, 42);
  let bytes = len_of(&s);
  let chars = borrow imm s { t -> count_chars(t) };
  let v = borrow imm s { t -> with default(0 - 1) { value(t) } };
  let s = print_string(s);
  print_lit(": ");
  print_dec(bytes);
  print_lit(" bytes, ");
  print_dec(chars);
  print_lit(" characters, value ");
  print_dec(v);
  print_byte(b'\n')
-- output: café = 42: 10 bytes, 9 characters, value 42

buf

buf A, a growable array, and a priority queue on one. Vec[T] wraps a buf T, and every function here works on both. borrow imm b { v -> … } and borrow mut b { v -> … } lend the elements as a view for slice. Buffers and strings teaches them.

namesignature
box buf[A](cap)buf Aan empty buffer with room for cap elements (buf_new[A](cap))
buf_of_arraybuf_of_array[A](a: array A) -> buf Aan array as a full buffer
array_tabarray_tab[A](n: i32, f: !(i32 => A)) -> array Aan array of f(0), …, f(n - 1)
array_setarray_set[A](a: array A, i: i32, x: A) -> array Ax in slot i of an array
len_oflen_of[A](b: buf A) -> buf A * i32the length
cap_ofcap_of[A](b: buf A) -> buf A * i32the capacity
pushpush[A](b: buf A, x: A) -> buf A * 1x at the end, growing when full
poppop[A](b: buf A) -> buf A * (A + 1)the last element taken out
taketake[A](b: buf A, i: i32) -> buf A * (A + 1)element i moved out, an empty element left in its place
truncatetruncate[A](b: buf A, k: i32) -> buf Athe first k kept, the rest consumed
clearclear[A](b: buf A) -> buf Aevery element consumed, the capacity kept
reservereserve[A](b: buf A, extra: i32) -> buf Aroom for extra more
growgrow[A](b: buf A, cap: i32) -> buf Amoved to capacity cap
heap_pushheap_push[A](h: buf (i32 * A), k: i32, x: A) -> buf (i32 * A)x added with priority k to a min-heap
heap_popheap_pop[A](h: buf (i32 * A)) -> buf (i32 * A) * ((i32 * A) + 1)the smallest priority and its element, taken out
heap_peekheap_peek[A](h: buf (i32 * A)) -> buf (i32 * A) * ((i32 * A) + 1)the same, left in place

An element type needs a placeholder for the spare capacity: numbers, views, arrays, buffers and data built from them have one; a heap cell ref A doesn’t. A heap’s elements are copied where they lie, so its A is copyable (i32, bool, tuples of them).

include textio

main -> 1 =
  let h = box buf[i32 * i32](4);
  let h = heap_push(h, 5, 50);
  let h = heap_push(h, 2, 20);
  let mut h = heap_push(h, 9, 90);
  for i in 0..3 {
    let r = heap_pop(&mut h);
    match r { inl (k, x) -> print_dec(x); print_byte(b' ') | inr u -> () }
  };
  let mut b = box buf[i32](2);
  push(&mut b, 1); push(&mut b, 2); push(&mut b, 3);
  let mut b = truncate(b, 1);
  let c = cap_of(&b);
  let n = len_of(&b);
  print_dec(n); print_byte(b' '); print_dec(c);
  print_byte(b'\n')
-- output: 20 50 90 1 8

slice

Functions on views: array_imm 'a A (shared) and array_mut 'a A (exclusive), lent from an array or a buffer by a borrow block. A function that changes a view hands it back, and discard_mut ends it. An index out of range reads the default and writes nothing. Searches and sorts take the element comparison as an argument, and functions that copy an element need a copyable A. Arrays teaches them.

namesignature
eq_u8, eq_i32eq_i32(x: i32, y: i32) -> boolelement equality, to pass to a search
cmp_u8, cmp_i32cmp_i32(x: i32, y: i32) -> i32element order, -1, 0 or 1 (u8 unsigned)
lenlen[A]<'a>(v: array_imm 'a A) -> i32the length
len_mutlen_mut[A]<'a>(v: array_mut 'a A) -> array_mut 'a A * i32the length of an exclusive view
atat[A]<'a>(v: array_imm 'a A, i: i32, d: A) -> Aa copy of element i, or d
getget[A]<'a>(v: array_imm 'a A, i: i32) -> imm 'a A + 1element i, lent
get_mutget_mut[A]<'a>(v: array_mut 'a A, i: i32) -> mut 'a A + 1element i, lent mut; the rest of the view is given up
readread[A]<'a>(g: imm 'a A) -> Aa copy of what a borrow points at, as *g
peekpeek[A]<'a>(v: array_mut 'a A, i: i32, d: A) -> array_mut 'a A * Aa copy of element i through an exclusive view
setset[A]<'a>(v: array_mut 'a A, i: i32, x: A) -> array_mut 'a Ax in slot i
replacereplace[A]<'a>(v: array_mut 'a A, i: i32, x: A) -> array_mut 'a A * (A + A)x in slot i; inl the old element, or inr x
swap_atswap_at[A]<'a>(v: array_mut 'a A, i: i32, j: i32) -> array_mut 'a Aslots i and j exchanged
discard_mutdiscard_mut[A]<'a>(v: array_mut 'a A) -> 1the view given up
empty, empty_mutempty[A]<'a>() -> array_imm 'a Aan empty view
split_atsplit_at[A]<'a>(v: array_imm 'a A, i: i32) -> array_imm 'a A * array_imm 'a A + array_imm 'a Athe first i and the rest, or inr v past the end
split_at_mutthe same over array_mut
split_first, split_lastsplit_first[A]<'a>(v: array_imm 'a A) -> imm 'a A * array_imm 'a A + 1the first (last) element and the rest
split_first_mut, split_last_mutthe same over array_mut
reversereverse[A]<'a>(v: array_mut 'a A) -> array_mut 'a Areversed in place
fillfill[A]<'a>(v: array_mut 'a A, x: A) -> array_mut 'a Ax in every slot
copy_fromcopy_from[A]<'a, 'b>(dst: array_mut 'a A, src: array_imm 'b A) -> array_mut 'a Asrc copied in, as far as the shorter
sortsort[A]<'a>(v: array_mut 'a A, c: !(A * A => i32), d: A) -> array_mut 'a Asorted by c, stably; d is any element
findfind[A]<'a>(v: array_imm 'a A, x: A, eq: !(A * A => bool)) -> i32 + 1the index of the first element equal to x
containscontains[A]<'a>(v: array_imm 'a A, x: A, eq: !(A * A => bool)) -> boolwhether an element equals x
binary_searchbinary_search[A]<'a>(v: array_imm 'a A, x: A, c: !(A * A => i32)) -> i32 + i32in a sorted view, inl an index of x, or inr where it would go
cmpcmp[A]<'a, 'b>(v: array_imm 'a A, w: array_imm 'b A, c: !(A * A => i32)) -> i32lexicographic order
eqeq[A]<'a, 'b>(v: array_imm 'a A, w: array_imm 'b A, e: !(A * A => bool)) -> boolthe same elements
starts_with, ends_withstarts_with[A]<'a, 'b>(v: array_imm 'a A, p: array_imm 'b A, e: !(A * A => bool)) -> bool
include textio
include slice

fn scramble(i: i32) -> i32 = (i * 7) % 10

main -> 1 =
  let mut a = array_new[i32](6, scramble);
  borrow mut a { v -> let v = sort(v, cmp_i32, 0); let v = reverse(v); discard_mut(v) };
  let at5 = borrow imm a { v -> find(v, 5, eq_i32) };
  let has3 = borrow imm a { v -> contains(v, 3, eq_i32) };
  for x in &a { print_dec(*x); print_byte(b' ') };
  match at5 { inl i -> print_lit("5 at "); print_dec(i) | inr u -> print_lit("no 5") };
  if has3 { print_lit(", has 3") } else { print_lit(", no 3") };
  print_byte(b'\n')
-- output: 8 7 5 4 1 0 5 at 2, no 3

key

The families a map uses: one equality, hash and order per type. They have cases at the integers, 1, tuples, sums (so bool), arrays, views and buffers (so string and text), compared structurally, and a program adds cases at its own types with poly. A float has none. Equality, hashing and order teaches them.

namesignature
equalpoly equal[T]<'a, 'b>(x: view 'a T, y: view 'b T) -> boolequality
hashpoly hash[T]<'a>(x: view 'a T) -> i64a deterministic hash, the same on every run
orderpoly order[T]<'a, 'b>(x: view 'a T, y: view 'b T) -> i32-1, 0 or 1

A view 'a T of an integer, a tuple of them or a view is the value itself, so equal(x, y) takes the values.

include textio
include key

main -> 1 =
  if equal((1, true), (1, true)) { print_lit("equal ") } else { print_lit("different ") };
  print_dec(order((1, 5), (2, 0)));
  print_byte(b' ');
  if hash(7) == hash(7) { print_lit("same hash") } else { print_lit("?") };
  print_byte(b'\n')
-- output: equal -1 same hash

map

A hash map and a set, keyed by key’s families. A map is a value: an operation takes it with &m or &mut m. borrow imm m { 'x, r -> … } lends it as r : MapRef[K, V]<'h, 'k, 'x>, to read keys and values in place. Maps and sets teaches them.

namesignature
Map[K, V], Set[K]typesa set is a map to 1
map_newmap_new[K, V](cap: i32) -> Map[K, V]an empty map with room for cap entries
map_insertmap_insert[K, V](m: Map[K, V], k: K, v: V) -> Map[K, V] * (V + 1)v at k; the value displaced
map_lookupmap_lookup[K, V](m: Map[K, V], k: K) -> Map[K, V] * (V + 1)a copy of the value at k (V copyable)
map_containsmap_contains[K, V](m: Map[K, V], k: K) -> Map[K, V] * boolwhether k has a value
map_removemap_remove[K, V](m: Map[K, V], k: K) -> Map[K, V] * (V + 1)the entry at k taken out
map_lenmap_len[K, V](m: Map[K, V]) -> Map[K, V] * i32the number of entries
map_getmap_get[K, V]<'h, 'k, 'x>(r: MapRef[K, V]<'h, 'k, 'x>, k: K) -> imm 'x V + 1the value at k, lent
map_sizemap_size[K, V]<'h, 'k, 'x>(r: MapRef[K, V]<'h, 'k, 'x>) -> i32the number of entries of a lent map
map_entriesmap_entries(r)a for source: each key and its value lent, imm 'k K * imm 'x V, in slot order
map_itermap_iter(r)a for source: each key copied (a key that is its own view: an integer, a text) with a view of its value, K * imm 'x V
map_foldmap_fold[K, V, S]<'h, 'k, 'x>(r: MapRef[K, V]<'h, 'k, 'x>, f: !((imm 'k K * imm 'x V) * S => S), s: S) -> Sf over the entries
map_drainmap_drain(m)a for source that consumes m: each entry taken out, K * V; what a break leaves is consumed
set_newset_new[K](cap: i32) -> Set[K]an empty set
set_insertset_insert[K](s: Set[K], k: K) -> Set[K] * boolk added; whether it was new
set_containsset_contains[K](s: Set[K], k: K) -> Set[K] * bool
set_removeset_remove[K](s: Set[K], k: K) -> Set[K] * boolk taken out; whether it was there
set_lenset_len[K](s: Set[K]) -> Set[K] * i32

The keys and values need a placeholder, as a buffer’s elements do. Every iteration is in slot order: the same on every run for the same insertions and removals, but neither insertion order nor sorted.

include textio
include map

main -> 1 =
  let mut seen = set_new[i32](8);
  let mut dups = 0;
  for x in 0..20 {
    let fresh = set_insert(&mut seen, (x * x) % 7);
    if not(fresh) { dups = dups + 1 }
  };
  let mut m = map_new[i32, i32](8);
  let old = map_insert(&mut m, 1, 10);
  let old = map_insert(&mut m, 1, 11);
  let gone = map_remove(&mut m, 2);
  let n = set_len(&seen);
  print_dec(n);
  print_byte(b' ');
  print_dec(dups);
  print_byte(b' ');
  match old { Some(v) -> print_dec(v) | None -> print_lit("none") };
  print_byte(b' ');
  match gone { Some(v) -> print_dec(v) | None -> print_lit("none") };
  print_byte(b'\n')
-- output: 4 16 10 none

list

A generic owned list. Its functions are recursive, so a call needs a rec handler around it. Recursion introduces it.

namesignature
List[A]typea list of As
nilnil[A]() -> List[A]the empty list
conscons[A](x: A, xs: List[A]) -> List[A]x in front of xs
mapmap[A, B](xs: List[A], f: !(A => B)) -> List[B]each x as f(x)
filterfilter[A](xs: List[A], p: !(A => bool)) -> List[A]the elements p holds of (A copyable)
foldfold[A, B](xs: List[A], z: B, f: !(B * A => B)) -> Bcombined from the left, from z
include textio
include list

main -> 1 =
  let xs = cons(1, cons(2, cons(3, cons(4, nil()))));
  let sum = with on_exhausted(!{ u -> 0 - 1 }) {
    fold(filter(map(xs, !{ x -> x * x }), !{ x -> x % 2 == 0 }), 0, !{ (a, x) -> a + x })
  };
  print_dec(sum);
  print_byte(b'\n')
-- output: 20

list’s map and filter share their names with those of adapters and iters, so a program that calls them includes only one of these modules.

yield

The effect a generator performs. Generators teaches it.

namesignature
Yield[T]effect Yield[T] { ctl yield(x: T) }
yield(x)x: T, answers ()x to the for running the generator

adapters

Generic generators over stored generators. A source U (pull A K R) yields As, returns K when it finishes, and hands back R when closed early (Stored generators). An adapter that runs its source to the end returns the source’s K; those that may stop it early take a source with R = K. Chain them with |> (Pipelines).

namesignatureyields
mapmap[A, B, K, R](it: U (pull A K R), f: !(A => B)) -> K with Yield[B]f(x) for each x
filterfilter[A, K, R](it: U (pull A K R), p: !(A => bool)) -> K with Yield[A]the elements p holds of (A copyable)
taketake[A, K](it: U (pull A K K), n: i32) -> K with Yield[A]the first n
skipskip[A, K, R](it: U (pull A K R), n: i32) -> K with Yield[A]all after the first n
take_untiltake_until[A, K](it: U (pull A K K), p: !(A => bool)) -> K with Yield[A]the elements before the first p holds of
enumerateenumerate[A, K, R](it: U (pull A K R)) -> K with Yield[i32 * A]each element with its position
chainchain[A, K, R](a: U (pull A K R), b: U (pull A 1 1)) -> K with Yield[A]a’s elements, then b’s
flat_mapflat_map[A, B, K, R](it: U (pull A K R), f: !(A => U (pull B 1 1))) -> K with Yield[B]the elements of each f(x)
scanscan[A, S, K, R](it: U (pull A K R), s: S, f: !(S * A => S)) -> K with Yield[S]the running states f(s, x)
zipzip[A, K, B](a: U (pull A K K), b: U (pull B 1 1)) -> K with Yield[A * B]pairs, until either ends
chunkschunks[A, K, R](it: U (pull A K R), n: i32) -> K with Yield[List[A]]lists of n, each last first
batchbatch[A, S, K, R](it: U (pull A K R), s0: S, add: !(S * A => S * bool)) -> K with Yield[S]states filled by add until it says full
lineslines[K, R](it: U (pull u8 K R)) -> K with Yield[List[u8]]a byte source’s lines, each last first
include textio
include adapters

fn range(lo: i32, hi: i32) -> 1 with Yield[i32] =
  let mut i = lo;
  while i < hi { yield(i); i = i + 1 };
  ()

main -> 1 =
  let mut last = 0;
  for s in range(1, 100) |> skip(2) |> scan(0, !{ (acc, x) -> acc + x }) |> take_until(!{ s -> s > 40 }) {
    last = s
  };
  let mut pairs = 0;
  for p in zip(range(0, 10), range(100, 103)) { let (a, b) = p; pairs = pairs + a * b };
  print_dec(last);
  print_byte(b' ');
  print_dec(pairs);
  print_byte(b'\n')
-- output: 33 305

iters

Adapters over data, a view or a count a..b. A for over a chain of them runs at most once per element, so it stays total. for over data covers the loop they feed.

nametakesyields
elems(v)v: array_imm 'a Aeach element, lent: imm 'a A
elems_mut(v)v: array_mut 'a Aeach element, lent mut: mut 'a A
copied(it)an iterator of imm 'a Aeach element copied: A
map(it, f)f: !(A => B)f(x)
filter(it, p)p: !(A => bool)the elements p holds of
take(it, k)k: i32the first k
enumerate(it)i32 * A: each with its position
zip(a, b)two iteratorspairs, until either ends
include textio
include iters

fn big(x: i32) -> bool = x > 2

total fn weighted<'a>(v: array_imm 'a i32) -> i32 =
  let mut s = 0;
  for p in zip(filter(copied(elems(v)), big), 100..200) { let (x, y) = p; s = s + x * y };
  s

main -> 1 =
  let a = array_new[i32](5, !{ i -> i });
  let w = borrow imm a { v -> weighted(v) };
  print_dec(w);
  print_byte(b'\n')
-- output: 704

async

Tasks and a round-robin scheduler, written in the language. Async tasks teaches them.

namesignature
Asynceffect Async { ctl await(); ctl log(x: i32) }let other tasks run; record an entry
spawnwith spawn { e }, a Task[A]e run to its first await
resumeresume[A](t: Task[A]) -> A + Task[A]run to the next await: inl the value, or inr the rest
cancelcancel[A](t: Task[A]) -> 1a task cancelled, what it owns freed
queuequeue[A]() -> Queue[A]an empty queue
pushpush[A](q: Queue[A], t: Task[A]) -> Queue[A]a task queued
run_until_emptyrun_until_empty[A](q: Queue[A]) -> List[A]the tasks’ values, in the order queued
spawn_loggedwith spawn_logged { e }, a Stopped[A]a task whose log entries are kept
lresume, lcancellresume[A](t: LTask[A]) -> Stopped[A]resume and cancel for a logged task
lqueuelqueue[A]() -> LQueue[A] * List[i32]an empty logged queue
lpushlpush[A](q: LQueue[A] * List[i32], s: Stopped[A]) -> LQueue[A] * List[i32]a logged task queued
run_loggedrun_logged[A](q: LQueue[A] * List[i32]) -> List[A] * List[i32]the values, and every entry logged, in order
spawn_inwith spawn_in { e }, a TaskIn[A]<'d>a task that may hold a borrow from the scope it was spawned in
resume_in, cancel_inresume_in[A]<'d>(t: TaskIn[A]<'d>) -> A + TaskIn[A]<'d>
queue_in, push_inpush_in[A]<'d>(q: QueueIn[A]<'d>, t: TaskIn[A]<'d>) -> QueueIn[A]<'d>
run_inrun_in[A]<'d>(q: QueueIn[A]<'d>, rounds: i32) -> List[A] with Exhaustedat most rounds rounds; a task still pending makes it perform exhausted()

async’s push shares its name with buf’s, so a program that queues tasks doesn’t push to a buffer by that name.

float

The float operations by name, conversions, IEEE 754’s total order, and printing and parsing in the shortest decimal that reads back as the same float. Each name below exists at f64 and, with f32 in place of f64, at f32. Floats introduces them.

namesignature
sqrt_f64, abs_f64, neg_f64, floor_f64, ceil_f64, trunc_f64(x: f64) -> f64
round_f64(x: f64) -> f64to the nearest integer, ties to even
min_f64, max_f64, copysign_f64(x: f64, y: f64) -> f64
is_nan_f64(x: f64) -> bool
f64_of_i32, f64_of_i64(n: i32) -> f64correctly rounded
i32_of_f64, i64_of_f64(x: f64) -> i32 + 1truncated; inr () for a NaN or out of range
i32_of_f64_sat, i64_of_f64_sat(x: f64) -> i32truncated; a NaN is 0, out of range the nearest end
f64_of_f32, f32_of_f64(x: f32) -> f64
bits_f64, of_bits_f64(x: f64) -> i64the bit pattern, and back
total_cmp_f64(x: f64, y: f64) -> i32IEEE 754’s totalOrder: -1, 0 or 1
ord_f64, of_ord_f64(x: f64) -> i64an integer key in totalOrder, usable in a map, and back
push_f64push_f64(s: string, x: f64) -> string * 1x appended, called with &mut s
print_f64print_f64(x: f64) -> 1x to standard output
parse_f64parse_f64<'a>(t: text 'a) -> f64 with Fail[FloatParseError]the float nearest the decimal t
FloatParseErrortype FloatParseError = 1 + u8the text ended early, or the byte that can’t stand there
include float

main -> 1 =
  let x = borrow imm "2.5e-3" { t -> with default(0.0) { parse_f64(t) } };
  let mut s = box string(32);
  push_f64(&mut s, x * 4.0);
  push_lit(&mut s, " ");
  push_f32(&mut s, f32_of_f64(0.1));
  let s = print_string(s);
  print_byte(b' ');
  print_dec(total_cmp_f64(neg_f64(0.0), 0.0));
  print_byte(b' ');
  match i32_of_f64(1.0e12) { inl n -> print_dec(n) | inr u -> print_lit("out of range") };
  print_byte(b'\n')
-- output: 0.01 0.1 -1 out of range