Conventions
A handful of patterns repeat across the stdlib:
- 1-based indexing everywhere.
String.sub,String.iter,Table.*, array literals — all 1-based. Negative indices count from the end where it makes sense. ?means “may not happen”. Lookups that can miss (String.find,Io.open,Os.getenv) return nullable types so the typechecker forces you to handle the absence with?.,??,!, or amatch.- Multi-return is a tuple. Functions like
String.findandOs.capturereturn(integer?, integer?)/(integer, string, string). Destructure withlocal s, e = ...or pattern-match. - Conversion is a cast, not a call.
asconverts betweenintegerandfloat, renders either (andboolean) as astring, and parses astringback — and on ananyit is the checked type test instead. It refuses any pair with no obvious answer rather than inventing one. The one pair that is deliberately a call isbytes↔string:Bytes.toStringcan fail, and a cast has nowhere to put the failure, so it isBytes’ job and its return type isstring?. - Numeric flavour never mixes.
integerandfloatare separate types and nothing promotes between them silently — not the operators, and not the stdlib.Math.*is generic over the flavour (<N>), so a call takes either and answers in kind, but one call may not take both. - No exceptions for “expected” failures. Filesystem helpers return
booleanrather than throwing. Usethrow/try/catchfor genuinely exceptional cases — see Language guide §Error Handling. The exception isOs.list, which throws; see Failure conventions. - Mutate with
Table, transform withIter.Table.*writes to the table it is given or answers a question about it;Iter.*builds a new sequence and never writes. Where both could claim a name they are named apart —Table.reverse/Iter.reverse,Table.indexOf/Iter.findIndex.
Anything not in this reference is not in the toolchain. Libraries — JSON among
them — ship as ordinary Saule packages: a git repo with a saule.config,
added to dependencies:. See
Language guide §Importing from a Dependency.