The standard library contract
Partly written. §1 was written with milestone M3 of the Lyric 5 plan; the rest comes with M8a–M10, spec-first: each rule lands here with its conformance case before or with its implementation. Source of the decisions: 10 Standard library.
Scope
What of the standard library is language: the prelude, the core interfaces and their contracts (hash, order, clone), the answer forms and the panic rule, the naming law, the syntax tree as values (std.syntax), serialization as a pair (std.codec), JSON (std.json), TOML (std.toml).
1. The converters
The functions of std.string that give a scalar's text — the text an f-string hole writes
(§2 rule 4) — and the one + on strings compiles to, by name
(10 S-series): a
program need not import concat for + to work, and a program that imports them gets the
same functions.
| Function | Text |
|---|---|
concat(a: string, b: string): string |
the two joined; what + on strings is |
fromInt(value: int): string |
the decimal digits, - when negative (03 §1.7) |
fromUint(value: uint): string |
the decimal digits |
fromFloat(value: float): string |
the shortest text that reads back as the value, 1.0 for an integral one; inf, -inf, nan (03 §1.7) |
fromFloat32(value: float32): string |
the shortest text that reads back as the same float32 — 0.1, not the float's 0.10000000149011612 (the review's M8a-9) |
fromBool(value: bool): string |
true or false |
fromChar(value: char): string |
the character, UTF-8 |
A narrower integer in a hole writes its value's text, widened (03 §1.3):
f"{x}" with x: int8 writes what fromInt(x) gives. A float32 has a converter of its own:
with x: float32 the hole writes what fromFloat32(x) gives.
2. The format language
A hole may name a format after a colon — {x:>8}, {n:#x}, {f:.2f}, {v:?} — in one
language, without locale, checked where it is written
(08 Y7,
10 S7).
- Form:
[[fill]align][sign][#][0][width][grouping][.precision][type].alignis<(left),>(right) or^(centred, the odd fill after), the fill any character before it, a space without;signis+(a sign on every number) or-(on a negative one only, as without);#writes a radix's prefix,0b,0o,0x,0X;0pads with zeros between the sign and prefix and the digits — with a grouping, the zeros are grouped with the digits and the width counts the separators, a minimum that no separator leads (the review's A4):{1234:010,}is00,001,234,{1234:08_}is0_001_234;widthis the least number of characters, not bytes;groupingis,or_between each three decimal digits,_between each four of another radix;precisionis the digits after a float's point or the most characters of a string;typeisb,o,x,X(an integer's radix),e,E,f,%(a float's form) or?(Debug). - What applies: an integer takes all but a precision and a float's type; a float all but
#and an integer's type; astringfill, alignment, width and precision; achar, abooland aDisplaytype fill, alignment and width, on its text — for aDisplaytype, whatshowTowrites;?takes fill, alignment and width, on whatdebugTowrites, for every type. A spec that is no format, or a part that does not apply to the hole's type, is refused (LYR-SEM0164). A type with formats of its own conforms toFormat { fn format(spec: StringView, out: StringBuilder): void; }and reads the spec as written, unchecked —?staysDebug. - Defaults: a number right-aligned, the rest left-aligned. A float without a type or a
precision is its shortest text (03 §1.7); with a precision and no type it is
fixed,
{2.5:.3}is2.500;e,E,fand%take six digits without a precision;%writes the value a hundredfold, fixed, with%after; an exponent carries its sign and two digits at least,{1234.5:e}is1.234500e+03. The fixed, the exponent and the percent forms round half away from zero on the value's exact binary digits — the language's one rounding rule,round()'s (the review's M8a-8), the same on every host:{0.125:.2f}is0.13,{2.5:.0f}is3, and{2.675:.2f}is2.67, the double nearest 2.675 lying below the half.nan,infand-infkeep their words,NANandINFunderE. - Binding: an f-string writes its text into one
StringBuilder, piece by piece in order (design 10 S6) — no string per hole and no chain of joins: a scalar's hole its text (§1), anotherDisplaytype's throughshowTo;{x:spec}throughformat(spec, out)for aFormattype — the scalars conform —, through the padding of whatshowTowrites for anotherDisplaytype, and throughdebugTounder?. The calls arestd.core's, bound by the compiler: a program need not import anything for an f-string. - At run time:
std.fmt.format(template: string, args: Display...): stringreads its template when it runs, in this language (design 08 Y7, 10 S7):{}takes the next value and{i}the i-th from 0 — the two mix,{}counting only itself —, either with:specas rules 1–3 say,?for the value'sDebugtext;{{and}}write a brace, and a value no hole names is left out. A value is passed as aDisplay(05 §2), a scalar's too. What the compiler refuses in an f-string panics here (LYR-RT0008, design 10 B3): a brace left open or closed without being opened, a value named that is not given, a spec that is no format or holds a part the value's type does not take,?on a value withoutDebug.
Numbers
The ten number types conform, in std.core, to a tower of interfaces (design 10 B5 Z1), whose
parent lists fix the operators' Out to Self (05 §8 rule 7), so
arithmetic stays in the type:
| Interface | Parents | Members | Conformers |
|---|---|---|---|
Num |
Equatable, Ordered, Add, Sub, Mul, Div, Rem (each <Out = Self>), Display, Debug, Parse, Default |
static let zero, one: Self; isZero(): bool |
all ten |
Signed |
Num, Neg<Out = Self> |
none of its own: what std.math's abs and signum take |
int8…int, float32, float |
Integer |
Num, TotalOrder, Hashable, the bit operators (each <Out = Self>) |
static let min, max: Self; static let bitWidth: int, isSigned: bool |
int8…int, uint8…uint |
Float |
Signed |
static let epsilon, infinity, nan, min, max, leastPositive: Self; static fn fromBits(b: uint64): Self; the members below |
float32, float |
The prelude passes Num, Integer and Float on (design 10 B2); Signed is std.core's.
isZero is a default, each type's own (05 §1 rule 4). What one computes
with a number — its absolute value, its sign, a root, an angle — is no member of the type but
std.math's (design 10 B5 Z2, "Review 2026-10-12"; below): what a number is stands at its
type, what one does with numbers in std.math.
Where an integer's arithmetic would leave the type, and its bits, are std.math's — free
functions over Integer, as computing with a number is (below; design 10 P8):
checkedAdd(a, b),checkedSub,checkedMul,checkedDiv,checkedRemgive?T:nullwhere the result does not fit, or the divisor is zero;min / -1does not fit, and a remainder by-1is0.saturatingAdd(a, b),saturatingSub,saturatingMulhold atminormax, by the sign the result would have.wrappingAdd(a, b),wrappingSub,wrappingMulreduce modulo 2^bitWidth, as+%,-%,*%do a builtin's (08);wrappingDivgivesminformin / -1andwrappingRem0for a remainder by-1— a zero divisor panics as/does.leadingZeros(x),trailingZeros(x)andpopCount(x)count bits at the type's width (bitWidthfor zero);rotateLeft(x, n)androtateRight(x, n)rotate modulo the width, a negativenthe other way.
Float describes the type in its statics (design 10 B5 Z2): epsilon the distance from 1.0
to the next value above it, min the most negative finite value, max the greatest,
leastPositive the least positive one — a subnormal —, nan the quiet NaN with its sign clear.
fromBits(b) is the value a bit pattern names, a float32 the low 32 bits. Its members are
defaults over Self, what describes the value:
isNan,isInfinite,isFinite;totalCompare(o): Ordering, IEEE 754's total order: -NaN, -infinity, the negatives,-0.0,0.0, the positives, infinity, NaN, every value in its one place — the ordersortBytakes, wherefloathas noTotalOrderof its own;toBits(): uint64, afloat32's 32 bits in the low ones.
std.math, clamp, a char's code point
What one computes with numbers is std.math's (design 10 B5 Z2, "Review 2026-10-12"): free
functions, generic over the tower, so that a reader finds sqrt(x) where every language puts
it and the type keeps what describes it. The elementary functions over Float are computed in
float and rounded once to a float32. sqrt, floor, ceil, round and trunc are exact;
the rest — cbrt, exp, the logarithms, pow, the trigonometric and hyperbolic functions,
hypot — are one implementation's (musl's), which the runtime carries on every platform: a
program computes the same bits wherever it runs, and a comptime site computes the bits the
program would (11 §2):
floor(x),ceil(x),round(x)— a half away from zero —,trunc(x);sqrt(x),cbrt(x),exp(x),ln(x),log2(x),log10(x);sin,cos,tan,asin,acos,atan,sinh,cosh,tanh, in radians;atan2(y, x), the angle of the point (x, y), -π to π;hypot(x, y).
Powers: pow(x, e) over Pow, std.math's interface
Pow { type Exponent; fn pow(e: Self.Exponent): Self; }, whose exponent is the number's own
answer (design 10 P8): every integer type conforms with Exponent = uint and multiplies by
squaring, an overflow being one of * (03); float32 and float conform with the
float itself as the exponent, the C library's pow. So pow(2, 10) is 1024 and
pow(2.0, 0.5) the square root of two, and x.pow(e) is the same call.
Over Signed: abs(x), 0.0 for -0.0, which < does not tell from 0.0; signum(x),
-1, 0 or 1 in the type, a float's NaN staying NaN. uint is no Signed, and has neither.
What describes the world rather than a type: pi, e and tau, typed float —
math.pi as float32 for the narrow width. Beside them stand the functions of two numbers:
min(a, b)andmax(a, b)overOrdered,awhere the two are equal or unordered (a NaN);gcd(a, b)andlcm(a, b)overInteger, never negative,gcd(0, 0)is0; a result beyond the type overflows and panics —gcd(int.min, 0);lerp(a, b, t)overFloat,aatt = 0andbatt = 1exactly.
A narrowing that answers is a static of the target (Z4): int8.exact(v) is the int8 an
integer v of any type names, or null where it does not fit; int8.clamping(v) holds at
min or max instead. They are members of Integer (the review's M8a-7): each integer type
answers them, and a type parameter bounded by Integer reaches them — U.exact(v),
U.clamping(v).
clamp(x, lo, hi) over Ordered, beside min and max: lo below it, hi above it, the value
itself between; lo above hi is the program's error and panics, and a value unordered with the bounds
comes back as it is. A char's code point is c as uint32 (the review's A9e: the method that
said the same is gone), and char.fromUint32(n): ?char is
null for a surrogate or beyond 0x10FFFF (Z8), where n as char panics
(03 §1.4).
Iteration
The protocol for walks (design 10 B6) is std.core's; the prelude passes Iterator,
Iterable and FromIterator on.
Iterator { type Item; type Error :: [Error] = never; mut fn next(): ?Self.Item throws Self.Error; fn sizeHint(): (int, ?int) }:next()gives the next value, andnullat the end. A walk that can fail answers itsError, whichnext()throws; by default it throws nothing (07 §2 rule 6).sizeHint()says how many values are left, at least and at most where that is known; the default says(0, null). A type that is anIteratorandCloseablethrows inclose()only what itsErrorallows (LYR-SEM0174; the review's M8a-4): a loop that closes the iterator is covered by the clause that covers its pulls (07 §2 rule 7).Iterable { type Iter :: [Iterator]; fn iter(): Self.Iter; }: whatforwalks. Every iterator is iterable as itself,extend<I :: [Iterator]> I :: [Iterable<Iter = I>].DoubleEnded :: [Iterator] { mut fn nextBack(): ?Self.Item; }: the last value not yet given.- A
Range<T>of an integer type or ofcharis anIteratorofTandDoubleEnded:next()givesstartand moves it on,nextBack()movesendback and gives it. ARangeInclusive<T>is both too and givesendas well; its last value leavesstartpastend, orendbeforestartwhere that value is the type's last (its first, from the back) — no bound leaves the type. A range of characters has its values the scalar values in between, the surrogates passed over, its first'\0'and its last'\u{10FFFF}'. A range walked changes:startandendarevar. - A
Coroutine<T>is anIteratorofTandCloseable, built in (design 10 B6 I7): itsErroris what it throws,neverwhere nothing; it is anIdentity(design 06 A7) and conforms to nothing else. A loop over one pulls it and closes it on its way out (07 §2 rule 7): left early, its stack unwinds and its defers run. - The adapters are members of every iterator (design 10 B6 I3, I4), each giving a struct of
its own — monomorphized, nothing allocated. They are
std.iter's (M9a S11c): the prelude names that module, so its blocks reach every iterator in every module without an import (04 rule 5), and a program imports a struct where it writes the type,import std.iter { MapIter }. The protocol, the ranges and the iterators ofstd.core's own types staystd.core's. Each:it.map(f)gives aMapIter<I, U, E>(Mapis the collection's),it.filter(p)aFilterIter<I, E>;Edefaults tonever, soMapIter<I, U>names the one whose function throws nothing. Every iterator type's name ends inIter(the review's A9g): the adapters (TakeIter,ZipIter, …), the string's (CharsIter,SplitIter<P>,MatchesIter<P>,LinesIter), the map's (MapEntriesIter,MapKeysIter,MapValuesIter), the slice's and the list's (SliceIter,ListIter). They pull lazily, one value at a time, and theirErroris the inner iterator's, joined with what a function they take throws (below). Of one source besides:take(n)andskip(n);takeWhile(p), which stays ended oncepfailed, andskipWhile(p);stepBy(n), the first value and then everynth,nbelow 1 a panic;enumerate(), each value with its position from 0,(int, Item);inspect(f), each value shown tofon its way through;mapNotNull(f), the values throughf, thenulls left out;flatMap(f), the values of theIterablesfgives, one after the other, andflatten()of an iterator ofIterables;dedup()of anEquatableitem anddedupBy(same), a run of valuessameholds for given once;scan(init, f),initand then each running valuefmakes of the one before and a value;peekable(), aPeekableIterwhosepeek()gives the next value without taking it;rev()of aDoubleEndediterator, its values from the back — noDoubleEndeditself, and throwing nothing, as a pull from the back throws nothing;cycle()of an iterator that isClone, its values over and over from a clone each time, none where it has none — an array's walk, a range and a string's characters areClone. Of two sources:zip(other), each value beside the other's as long as both have one, the other anIterable;chain(other), the values and then the other's, an iterator of the same items. An adapter over aCloseableiterator —rev()andcycle()excepted — isCloseableitself and passesclose()on (the review's M8a-4), so a loop that leavesgen().map(f)early closes the generator;zipandchainclose both of theirs, the first error winning. TheirErroris the join of their sources' (design 05 E2 K7),Join<A, B>ofstd.core: one type where both are one, the other where one isnever, the rootErrorwhere they differ — reduced once both are known; open, it covers each of its parts. - A function an adapter or a terminator takes may throw (design 05 K4; 13 M8c P2):
map,filter,takeWhile,skipWhileandinspecttake afn(…) -> … throws Eand give an iterator whoseErrorisJoin<I.Error, E>;fold,reduce,any,all,none,find,firstWhereandforEachthrowJoin<I.Error, E>.Eis what the function throws, bound at the call, andneverwhere it throws nothing — then nothing is joined and notrystands:words.iter().map((s) => try int.parse(s))is walked withtry,xs.iter().map((x) => x * 2)without. - The terminators walk an iterator — a copy of a struct one, a class one where it stands — and
throw what it throws:
count();fold(init, f)andreduce(f), the latternullwithout a value;first(),last()andnth(n),nullwhere there is no such value, a negativenincluded;any(p),all(p)(truefor none) andnone(p), stopping at the value that decides;find(p), the index of the first valuepholds for, andfirstWhere(p), that value —findgives an index wherever it stands, a text's, a slice's, an iterator's (design 10 P6);forEach(f), inline (03 §8.5): a lambda written at the call is set in place, itsreturnthe caller's;toArray(), in one pass;minBy(key)andmaxBy(key), the value whoseTotalOrderkey is least or greatest, the first of equal ones,nullwithout a value. Ofstd.collections:chunks(n), the values inLists ofn, the last shorter where they do not divide, andwindows(n), every run ofnvalues in turn, each aListof its own, none where there are fewer —nbelow 1 panics (LYR-RT0008);sorted()of aTotalOrderitem andsortedBy(cmp), the values in aList, stably sorted;partition(p), the valuespholds for and the others, twoLists in order;groupBy(key), aMapof the values inLists under their key, each list in order;toMap()of pairs, aMap, a later value of a key replacing the earlier one.collect()into an array or a list starts with the room the iterator'ssizeHint()gives; where that is exact, an array is made without a copy. FromIterator<T> { static fn fromIter<I :: [Iterator<Item = T>]>(it: I): Self throws I.Error; }is whatcollect<C :: [FromIterator<Item>]>()builds (design 10 B6, I4; M8a S15): the values in aC, the type the call's position expects —let names: Set<string> = words.iter().collect();—, throwing what the iterator throws.T[]is aFromIterator<T>, and so areList<T>,Deque<T>,Set<T>,Heap<T>andMap<K, V>of(K, V)(§Collections) — a set's and a map's keys once, a later value of a key replacing the earlier one.toSet(), of aHashableelement, iscollect()into aSet, astoList()is into aListandtoArray()into an array.- The terminators of an element kind stand on the iterators whose
Itemadmits them (05 §13 rule 2):sum()andproduct()of aNum, zero and one where there is no value;min()andmax()of anOrdered,nullwithout a value, the first of equal ones;join(sep)of aDisplay, the texts withsepbetween each two;average()ofintor offloatvalues, afloat,nullwithout a value. SliceIter<T>is the iterator of an array or a view, from both ends, through a view of its elements — nothing is copied, and a write to a later element is seen; itssizeHint()is exact.T[]andSlice<T>areIterablethrough it:xs.iter().
Parsing
A type read from a text conforms to Parse, static fn parse(s: StringView): Self throws ParseError (design 10 B4, B5 Z6): every number type through Num, and bool. The text is
taken whole — no whitespace, nothing after the value — and what is wrong with it is thrown,
never panicked (10 B3): a ParseError, whose kind is a ParseErrorKind — Empty for
no text, Invalid for a text not of the type's form, Overflow for a number beyond the type's
range. All three are std.core's. Beside the kind a parser says what it can: detail — what
it expected where it stopped, empty where it says nothing more — and offset, the byte of
the text the refusal points at, null where it knows none; message() carries both. The number
parsers say nothing more; Uri.parse, Percent.decode, the address parsers and the time parsers
fill them.
- An integer is an optional
+or-and ASCII digits,_only between two digits.parse(s, radix)stands besideparse(s)(08 §1.2) and reads digits inradix, 2 to 36 —a…zorA…Zfor 10 to 35; a radix outside is the program's error and panics. An unsigned type takes no-, not even before0. A byte outside the form makes the textInvalidhowever many digits run before it; only a well-formed number is anOverflow. - A float is an optional sign, then digits with an optional
.and fraction — one digit at least — and an optional exponent,eorEwith its own sign and digits,_only between two digits; or the sign andinfornan. The value is the float nearest the text: beyond the range an infinity, below it a zero, as IEEE 754 rounds — a float has noOverflow. - A bool is
trueorfalse.
Results and conversions
A failure kept as a value, and a value made from another (design 05 E1, 10 B3, B4) — both
std.core's, and named in the prelude (04).
Result<T, E :: [Error]>is an enum,Ok(T)orErr(E), matched as every enum is (09).throwsstays the mechanism: two bridges lead to it and back, and no operator is a result's own (05 E1).Result.of(f)callsf, afn() -> T throws E, and isOkwith what it returns orErrwith what it throws —Result.of { try half(n) },Einferred from what the lambda throws (05 K4), the rootErrorwhere that is two types.r.get()is anOk's value and throws anErr's error:try r.get().Eis anError, sinceget()throws it. The library returns noResult(10 B3); the caller makes one. It is@MustUse(11 §1 rule 11): a statement that drops one warns.From<T> { static fn from(v: T): Self; }is a conversion into a type, which the type writes:Centimeters.from(m).Into<T> { fn into(): T; }follows from it through std.core's blanketextend<T, U :: [From<T>]> T :: [Into<U>](05 §13 rule 9):let c: Centimeters = m.into();— the type the position expects chooses the target; where none is expected the call is refused (LYR-SEM0060), and a target that is noFromof the value's type too (LYR-SEM0028). A constraintT :: [Into<X>]holds for every typeXis aFromof.asconverts numbers only (03 §1.4).
Arrays and views
The members of an array and of a view are std.core's (design 10 C2), written once on
Slice<T> and reached on an array through a view of all of it (03 §5.2 rule 4)
— and so on an inline array that lies in the heap (03 §5.3 rule 6): a write
through one reaches the array. length() and the index are the primitives
(03 §5.1).
isEmpty();get(i),nulloutside0 ≤ i < length();first()andlast(),nullwhere there is no element.swap(i, j), an index outside panicking as[i]does;reverse()in place;reversed(), a new array —std.collections's, withfind,partitionPointandbinarySearchbelow: what searches in a slice or makes a new one of it is that module's, a block onSlice<T>the prelude passes on; what describes the slice and what changes it in place isstd.core's (design 10 P9).copyInto(into): the elements into the viewinto, which is at least as long — a shorter one panics as an index outside does (LYR-RT0003), a longer one keeps its rest; the two may overlap either way (the review's B16). One copy, not an element at a time: what the collections grow and shift with.- Of an
Equatableelement:contains(x), through whichT[]andSlice<T>areContains<T>(10 C12) — whatx in xsasks (05 §12 rule 10) — andfind(x), the first index ornull. - Of a
Cloneelement:fill(v), every element a clone ofv— no object shared into several slots, as[x] * nshares none (03 §5.1). partitionPoint(p): the first index whose element failsp, for elementsppartitions — every one it holds for before every one it does not; a binary search. Of aTotalOrderelement,binarySearch(x): the index of an element equal toxin sorted elements, any of several equal ones, ornull.- Of a
Displayelement:join(sep), the elements' texts withsepbetween each two —std.string's, as it makes a text (§Strings). - Clone (10 K5; N2a): of a
Cloneelement,T[]isClone—clone()is a new array of the elements' clones, and[xs] * nrepeats such an array byclone(03 §5.1). - Equality and display (10 C9; M8a S14): of an
Equatableelement,T[]andSlice<T>areEquatable, element by element in order —[1, 2] == [1, 2],xs[1..] == ys[1..]. Of aDebugelement they areDebugandDisplayalike:[1, 2, 3], each element in its debug text — a string quoted,["a", "b"], where unquoted its bounds would be lost — so{xs}and{xs:?}print the same, and a width pads it as aDisplayvalue's text. An array literal beside==or!=takes the other side's type,xs == [];[] == []fixes none (LYR-SEM0060). - The sorts (10 C10):
sortBy(cmp)puts the elements in the ordercmpgives, andsortByKey(key)in the order of aTotalOrderkey; of aTotalOrderelement,sort()in ascending order. All three are stable — equal elements keep their order — and take a buffer of the length.sortUnstable(), of aTotalOrderelement, sorts in place without one; equal elements end in any order. Each isO(n log n)in comparisons. Afloathas no total order of its own: its elements sort throughsortBy. The comparison ofsortBy, the key ofsortByKeyand the test ofpartitionPoint(p)may throw, and the call throws it: a sort that one ends leaves every element in the slice, in some order. arrayOf(n, f)(03 §8) callsfonce per index, in order; a negativenpanics; whatfthrows, the call throws, and no array is made.
Hashing
A hash is computed with a Hasher of std.core (design 10 Q3, K2): writeByte(b) writes a
byte; write(bytes) the bytes of a Slice<uint8>, writeUint(v) and writeInt(v) the eight
bytes of a word, the lowest first — defaults over writeByte a hasher may replace; finish()
answers the hash of what was written, a uint64, and leaves the hasher writable. std.hash
holds three:
DefaultHasher, SipHash-1-3 under a key the system draws once per process, at the first use — the same in every thread, another in the next run. What it hashes cannot be aimed at collisions from outside, and an order that follows its hashes differs from run to run.FixedHasher, SipHash-1-3 under a zero key: the same hash in every run.Fnv1a64, FNV-1a in 64 bits: keyless, for input no adversary chooses.
Where the system gives no random bytes, the program panics with LYR-RT0015
(13).
std.hash also holds three checksums of a Slice<uint8>, each a uint32 (design 10 Q3; M8c
S11). Each continues from a running value, as zlib's do: the checksum of the whole is that of its
pieces, one after the other — crc32(rest, crc32(first)).
crc32(bytes, running = 0), CRC-32 of ISO-HDLC (zip, gzip, PNG):crc32("123456789".bytes())is0xCBF43926.crc32c(bytes, running = 0), the Castagnoli polynomial (iSCSI, RFC 3720):0xE3069283over the same nine bytes.adler32(bytes, running = 1), RFC 1950's (zlib):adler32("Wikipedia".bytes())is0x11E60398.
Input and output
The core of std.io, its buffers and its text (design 10 O1–O3;
M8b S2, S3 and S10a). The files, the console's streams and the network have sections of their
own below.
IoErroris the one error that input and output throw (O3), a struct:kind: IoErrorKind,path: ?string,detail: stringandinner: ?Error— the field O3 callscause, named so becauseErrorasks for a methodcause()and a type's members do not share a name (asException'sinner). Itsmessage()is the kind in words, then the path and the detail where there are any:not found: data.txt (opened to read).IoErrorKindnames O3's kinds, andOther { code }a system's code no kind names. It is@NonExhaustive(11 §1 rule 12): a later release may name another kind, so amatchover it outsidestdwrites a_.Readerhasread(into: Slice<uint8>): int: it fills the slice from the front and answers how many bytes it put there — 0 only at the end, and at once for an empty slice; fewer than the slice holds is no end.Writerhaswrite(from: Slice<uint8>): int: it takes from the front and may take fewer bytes than it is handed.Seekhasseek(to: SeekFrom): int,SeekFrom.Start(n),.End(n)or.Current(n), and answers the position counted from the start. All three aremut fns.read,writeand a writer'sflush()— which hands on what it holds back; the default does nothing (design 10 B8) — throw[IoError, Cancelled],seekthrowsIoError. WhyCancelled(design 10 Review 2026-10-08): a read from a socket or a pipe, or a write into one, waits for as long as the other side does nothing, and a cancel (10 §1 rule 7) must be able to end that wait with the error every wait ends with — not with anIoError, which a handler would take for the stream's failure. A reader or writer that never waits without bound declaresIoErroralone, as an implementation may throw less than its member (06 §2 rule 4); code over any reader or writer is told both.- The defaults (O1), on every reader:
readExact(into)fills the slice whole or throwsUnexpectedEof;readToEnd()is everything to the end in a new array;readToString()the same as UTF-8 — bytes that are no UTF-8 throwInvalidData, the decoder'sUtf8Erroras the cause. On every writer:writeAll(from)writes until all of it is taken — a writer that takes no byte of a slice that has some throwsOther { code: 0 }rather than being asked forever — andwriteString(s)writes the text's UTF-8. They throw what any reader's or writer's call may,[IoError, Cancelled]— over one that declaresIoErroralone too. copy(r, w): intcopies everythingrholds intowand answers how many bytes, and throws[IoError, Cancelled]. The two are used as the values handed in: a class is itself, and a reader that is a struct is read through a copy, its position unmoved for the caller.- The streams over memory (O2):
ByteReader.new(bytes)reads aSlice<uint8>from the front and seeks — a position past the end reads nothing, one before the start panics;ByteBuffer.new()appends what is written and reads from the front what was written and not yet read, withlength()andtoArray()of what it holds. Both are classes, saythrows IoErrorand throw nothing; never waiting, they leaveCancelledout. - The buffers (O2), with 8 KiB of their own.
BufReader.new(r)is a reader that readsra buffer at a time; a read at least as large as the buffer, while it is empty, goes tordirectly.BufWriter.new(w)is a writer that holds back what it is handed until the buffer is full, aflush()or aclose(); a write at least as large goes towdirectly, after what is held. A flush that throws keeps whatwdid not take, for the next. Both throw what any reader's or writer's call may (rule 2). ABufWriterisCloseable:close()flushes and leaveswopen, and one that nothing closes is warned about (06 §7.4,LYR-SEM0144). (Informative.) O2 has both as structs with the bytes inline; every read hands the inner reader a view of the buffer, and an inline array in a frame has no view (03 §5.3), so they are classes over a buffer on the heap. TextReader.new(r, skipBom: false)reads UTF-8 text through a buffer of its own.readLine(): ?stringis the next line without its end — a\nor a\r\n; a\ralone is text — ornullat the end. A last line without an end is a line; the end of the last line starts none.lines()iterates the same;chars()iterates the codepoints;readToEnd(): stringis the rest. All throw[IoError, Cancelled], and the two iterators'ErrorisJoin<IoError, Cancelled>, the rootErroronce reduced (§Iteration): a loop over them is toldError. A codepoint split between two reads ofrwaits for its rest. Bytes that are no UTF-8 throwInvalidData, aUtf8Erroras the cause whose offset counts from the reader's start; a line that throws is consumed. A byte order mark is text,U+FEFF, unlessskipBom: truedrops it from the front.TextWriter.new(w):write(s)writes the text's UTF-8,writeLine(s)that and a\n, on every system. It holds nothing back, and throws whatwmay.
Encodings
std.encoding (design 10 Q5; M8b S4a): bytes as text and
back, and the integers of a byte slice. Each encoding is a struct of static members, a name
space: Base64.encode(bytes).
EncodingErroris what a decoder throws:offset, where the text first is not the encoding — a byte of it, a unit of UTF-16 —, anddetail, what is wrong there. Itsmessage()isno Base64 digit at offset 1.Base64andBase64Urlencode aSlice<uint8>with RFC 4648's alphabets (§4 with+and/, §5 with-and_), padded with=to a multiple of four characters.decodeis strict: a text whose length is no multiple of four fails at its end; a character outside the alphabet — whitespace and the other alphabet's two among them — or a=before the end fails where it stands; bits past the last byte that are not zero fail at their character (RFC 4648 §3.5).Base64Url.encodeUnpaddedanddecodeUnpadded(M10 S5; design 10 S4a) are the same alphabet without the=padding, as JOSE writes it (RFC 7515 §2): the text's length is 4k, 4k + 2 or 4k + 3 — a length of 4k + 1 fails at its end —, a=is no digit here, and the bits past the last byte are checked as above.Hexencodes two digits a byte, in small letters, and decodes either case; an odd count of digits fails at the end.Utf16encodes a text as code units,uint16[], a codepoint pastU+FFFFas a surrogate pair;decodetakes units, and half of a pair without its other half fails at its unit.- The integers of bytes:
T.fromBytesLE(s)andT.fromBytesBE(s)for every integer typeTread exactly its width — another length panics (LYR-RT0008) —, the first byte the lowest or the highest.Slice<uint8>hasgetUint16LE(at),getUint16BE, the same forUint32,Uint64,Int16,Int32andInt64: the width at the byte offsetat. The way back:v.toBytesLE()andv.toBytesBE()of every integer type give its width as an inline array,uint8[N]—(258 as uint16).toBytesBE()is[1, 2](M8c S11). An inline array in a frame gives no view (03 §5.3):T.fromBytesLE(v.toBytesLE().toArray())isv. They are members of the module's blocks, there wherestd.encodingis imported. (Informative.) A string's bytes arebytes()(§Strings).
Cryptography
std.crypto (design 10 Q4; M8b S4b): the hashes for security, HMAC,
the system's random bytes, and a comparison of secrets. The doors are Ed25519, X25519,
ChaCha20-Poly1305 and Argon2.
Digestis a hash fed in pieces:static let sizeandstatic let blockSizein bytes,static fn new(),update(bytes)as often as there are pieces, andfinish(): uint8[], the hash of all of them.finishleaves the digest as it was: fed further, it hashes the longer input. (Informative.) Q4 writesfinish(): uint8[N]; an interface names no length that is each implementer's own, as 5.0 has no generic constant (03 §5).Sha256andSha512are FIPS 180-4's SHA-256 and SHA-512,Sha1its SHA-1 andMd5RFC 1321's MD5. SHA-1 and MD5 are broken for collisions and are there for what an old format asks.sha256(bytes): uint8[32]is the hash at once.Hmac<D>.new(key)is RFC 2104's HMAC over anyDigest, withupdateandfinishas a digest's: a key longer thanD.blockSizeis hashed first, a shorter one padded with zeros.randomBytes(n): uint8[]andrandomUint64(): uint64come from the system; a system that gives none panics (LYR-RT0015).constantTimeEq(a, b): boolcompares two byte slices in a time that depends on their lengths alone — not on where they first differ. Slices of different lengths are unequal.
Random numbers
std.random (design 10 Q2; M8b S4c): a generator, its draws, and the free
forms over the running thread's own. Not for secrets — randomBytes of §Cryptography is.
Randomis ChaCha8 (RFC 8439's block function, eight rounds), 64 bits a draw.Random.seeded(seed: uint64)gives the stream the seed names, the same in every 5.x: the key is four outputs of SplitMix64 from the seed — eight words, the low half of each output first —, the nonce is zero, the block counter counts from zero in words 12 and 13, and each block's sixteen words are taken in order, two of them a draw, the first the low half.Random.fresh()keys a generator from the system's random bytes.- The draws.
nextUint64();nextInt(), the draw as anint;nextInRange(r), an integer of theRange<int>r, each alike — a draw below 2⁶⁴ modulo the range's length is drawn again —, an empty range panics (LYR-RT0008);nextFloat(), the high 53 bits of a draw over 2⁵³, in [0, 1);nextBool(), the high bit of a draw;nextBytes(into), eight bytes a draw, the low byte first, a shorter tail from one draw's low bytes;nextNormal(mean = 0.0, stdDev = 1.0), Marsaglia's polar method, two floats a try and one of the pair kept. Each takes its draws from the stream as said, so a seeded generator gives the same values in every 5.x —nextNormalup to the last bits of the platform'slnandsqrt. shuffle(s)puts aSlice<T>in an order each of its orders alike (Fisher–Yates, from the back, the placeiswapped with one of0..i + 1).choice(s): ?Tis an element, each alike, ornullfor an empty slice, which draws nothing.sample(s, k): T[]iskelements fromkdifferent places, in a random order;koutside0..=s.length()panics.- The free forms
random.nextInRange(r),random.nextFloat()andrandom.shuffle(s)draw from the running thread's generator — its own, keyed from the system at its first use there. (Informative.) Q2 names themrandom.intandrandom.float; no declaration takes a builtin type's name (07 K5), so they carryRandom's names.
Time
std.time (design 10 Q1; M8b S5a, S5b): spans, the system's two clocks,
the calendar and RFC 3339; the time zones' database and the format patterns are The zones
below.
Durationis a span in whole nanoseconds, negative or not:ofNanos,ofMicros,ofMillis,ofSecs,ofMinutes,ofHours;nanos(),millis()andsecs()rounded toward zero,asSecsFloat().+and-with aDuration,*and/with anint; arithmetic that leaves the range ofintpanics (LYR-RT0002), a division by zero too (LYR-RT0001). It isEquatable,Ordered,TotalOrder,Hashable, its default is zero.- A duration's text is Go's:
0s; under a second one unit —ns,µs,ms— with its fraction, the fraction's trailing zeros dropped (1.5µs,500ms); from a second on hours, minutes and seconds, from the first that is not zero, the seconds with their fraction (1.5s,1m30s,1h0m0s); a minus in front of a negative span. Duration.parse(s)reads Go's form: an optional sign, then one or more numbers each with a unit —ns,us(orµs,μs),ms,s,m,h—, a number with a fraction allowed (1.5h,.5s),0alone for zero. An empty text throwsParseErrorwithEmpty, a number without a unit or a unit unknown,Invalid, a span beyond the range,Overflow. A fraction is taken as Go takes it,fraction × (unit ÷ 10^digits)in floating point, so what Go reads this reads. A duration's text parses back to it.Instantis a moment on the wall clock, nanoseconds since1970-01-01T00:00:00Z, POSIX time without leap seconds:Instant.now(),Instant.ofEpochMillis(n),epochSecs()andepochMillis()rounded down,instant + duration,later.since(earlier): Duration. Instants compare and order as the moments they are.ofEpochMillisis the inverse ofepochMillisfor a whole millisecond, a negativenbefore the epoch; annwhose moment an instant cannot hold panics with an overflow. The system may set its wall clock back.Monotonicis a moment on the clock that only moves forward, from an unspecified start:Monotonic.now(),elapsed(): Duration.- The calendar is the proleptic Gregorian one, every year an
intholds.Date.of(year, month, day)andTime.of(hour, minute, second, nano = 0)throwTimeErrorfor what is no date — a month outside 1–12, a day beyond the month's — and no time of day — 24:00, a second 60, which POSIX time does not count. A year is a leap year divisible by 4, and by 400 where by 100 (isLeapYear()).plusDays(n)moves a date,dayOfWeek()is aWeekday,Mondayfirst. Dates and times compare and order; a date's text is ISO 8601'sYYYY-MM-DD— a year outside 0000–9999 with its sign and at least four digits —, a time'sHH:MM:SSand the fraction without trailing zeros. Both read back from their texts (Parse):Time.parse("09:30:00.5")takes up to nine digits of fraction and refuses an offset, which is aDateTime's. - A zone is a fixed offset from UTC:
Zone.utc,Zone.fixed(offset)— whole minutes under a day, elseTimeError. A zone of the database is in The zones below. DateTimeis a date and a time of day at an offset:DateTime.of(date, time, zone),instant.toDateTime(zone),toInstant(),date(),time(),offset(). Two compare field by field — one moment at two offsets is two date-times, one instant.- RFC 3339 (§5.6) is the text of an
Instant— in UTC,Z— and of aDateTime— its offset,Zfor none —, the fraction without trailing zeros, none where it is zero. Parsing is strict: aTortbetween date and time,Z,zor±hh:mm, a fraction of any length, cut to nanoseconds; a space for theT, a missing offset, a date or a time that does not exist isInvalid, an empty textEmpty.Date.parsereads the full-date alone.
The zones (design 10 Q1; M10 S11). Zone is UTC, a fixed offset, or a zone of the database:
- Where a zone comes from.
Zone.utc;Zone.fixed(offset)(whole minutes under a day);Zone.load(name)reads the IANA name's TZif file (RFC 8536, versions 1 to 4: the 64-bit block of a version 2+ file, the footer's rule for the years beyond the last transition, leap seconds read over) from the system's database —TZDIRor/usr/share/zoneinfo—,TimeError { kind = .NoZoneData }where the system has none (Windows),.NoSuchZonewhere it has no such zone or the name is none (empty, absolute, with..);Zone.fromTzif(name, bytes)andZone.fromPosix(name, tz)(a POSIX TZ string,CET-1CEST,M3.5.0,M10.5.0/3,EST5EDT,<+0330>-3:30; the offsets west-positive as POSIX writes them; daylight time without an offset one hour ahead, without a rule the US rule) take data of one's own;Zone.local()isTZwhere set (a name of the database, else a POSIX string), else/etc/localtime, else UTC.name(),isFixed(),offsetAt(instant),infoAt(instant)—ZoneInfo { offset, isDst, abbreviation }—;offset()of a zone of the database is its offset now. Two zones are equal by name and kind. - A moment in a zone.
instant.toDateTime(zone)takes the offset in force at the moment.DateTime.of(date, time, zone)resolves a local time: once, it is that moment; in the hour the clocks fall back, it is the earlier of the two moments; in the hour they spring forward, it moves forward by the gap (02:30on the morning of the change is03:30at the new offset). ADateTimekeeps its zone:zone(),zoneInfo(),withZone(zone)the same moment there,plusDays(n)the same local timendays on at the offset then in force (a day across the change is 23 or 25 hours). Two date-times are equal by date, time and offset; their zones' names are not compared. - Patterns (UTS #35, English).
dateTime.format(pattern):ythe year (yyits last two digits, moreypadded to the count),Mthe month (MMpadded,MMMJan,MMMMJanuary,MMMMMJ),dthe day,Hthe hour of 24,hof 12,m,s,S…the fraction to as many digits,aAM/PM,EtoEEETue,EEEETuesday,EEEEET,ZtoZZZ+0100,ZZZZGMT+01:00(GMTfor none),ZZZZZ+01:00(Zfor none),X/XX/XXX+01/+0100/+01:00withZfor none,x/xx/xxxthe same without; text in'…'as it is,''a quote, what is no letter as it is; a letter the pattern does not know or a quote not closed isTimeError.DateTime.parseWith(pattern, text, zone = Zone.utc)reads the same fields back: a number takes the digits it finds (a padded field at least its width), a name is matched without case, a fraction as many digits as there are,yyas 1970–2069; what the pattern does not name is 1970-01-01 00:00:00 UTC; an offset field makes a fixed zone, without one the zone given resolves the local time; what does not fit isParseError { kind = .Invalid }.
Paths
std.path (design 10 O6; M8b S6a): a path is a string, and there is no
Path type. These functions take a path apart and put one together lexically, as Go's
path/filepath does; none of them asks the file system.
- The system's forms.
path.separatoris\on Windows and/elsewhere. On Windows both\and/separate; a path may start with a volume — a drive,C:, or a UNC share,\\server\share—; two names compare without regard to the case of ASCII letters; and what these functions build, they build with\. normalize(p)is Go'sClean: separators run together,.elements dropped, a..taking the element before it away — above the root it is dropped, in a relative path kept —, no separator at the end but the root's, and.for nothing.join(parts...)joins the parts that are not empty by the separator and normalizes; it is empty for no such part.fileName(p)is the last element (Go'sBase),parent(p)the rest, normalized (Go'sDir),extension(p)the last element's extension with its dot, from its last dot (Go'sExt; of.bashrcthat is all of it),stem(p)the last element without it, andwithExtension(p, ext)the path with another extension — a dot added toextwhere it has none, the extension dropped for an empty one.isAbsolute(p)is Go'sIsAbs— on Windows a drive with the root, or a UNC share.relative(base, target): ?stringis Go'sRel: the path that namestargetfrombase,.for the same place,nullwhere none exists — one absolute and the other not, two volumes, a base that climbs above where the target is.components(p)lists the root — with its volume — and then the elements, without the empty ones and., keeping..; nothing is normalized.
Files
std.fs (design 10 O4, O5; M8b S6b, S6c): File, a handle on a file, and the
conveniences that read or write a file whole or its lines one by one. The directories are the
next section's.
File.open(path)opens to read,File.create(path)to write — made where it is not there, cut to nothing where it is —,File.openWith(path, options)as anOpenOptionssays:read,write,append(writes at the end),create,truncate. Options that read and write nothing, cut without writing from the start, or create without writing are refused (InvalidInput).- A file is a
Reader, aWriterand aSeekat its position, andCloseable. It reads and writes through the caller's slice. A read or a write may take part of it, as rule 2 of §Input and output allows. - What a file cannot do is an
IoErrorwith its path and what was done —opened to read,opened to write,read,written,sought,closed—:not found: data.txt (opened to read). The system's errors map toNotFound,PermissionDenied,AlreadyExists,IsDirectory,NotDirectoryandInvalidInput, and any other toOther { code }, the system's number. Whether a directory opens as a file and fails at the read, or fails to open, is the system's (POSIX opens it; the read saysIsDirectory). - A file is closed once: after
close()every call throwsClosed, and a secondclose()does nothing. AFilethat nothing closes is warned about (06 §7.4). - The I/O pool. A file's calls run on a few threads of the program's own, started at the
first call: the running task is parked meanwhile and its thread runs the other tasks. A call
on a file ends in a bounded time and is no wait in the sense of 10 §1
rule 7: in a cancelled task it runs to its end, and the cancellation is thrown at the task's
next wait. (Informative.) Ending early would hand the caller back a buffer the pool still
writes into. So a
FiledeclaresIoErroralone (§Input and output rule 2), and so do the conveniences below andFileLinesIter, though the defaults they use declareCancelledfor any reader: from a file it cannot come. - The conveniences read or write a file whole:
fs.readText(path), its UTF-8 — bytes that are no UTF-8 throwInvalidDatawith the path, and a byte order mark is text, as rule 7 of §Input and output keeps it —,fs.readBytes(path),fs.writeText(path, text)andfs.writeBytes(path, bytes), which make or cut the file asFile.createdoes, andfs.appendText(path, text), which writes at its end and makes it where it is not there. Each closes its file on every way out; what the close throws counts where nothing went wrong before (06 §7). fs.lines(path)gives the file's lines asTextReader'sreadLinegives them, through aFileLinesIterwithError = IoErrorthat isCloseable: it closes the file at its end and at its first error, a loop that leaves it early closes it (07 §2 rule 7), and so doesclose(). After its end or an error it gives no more; afterclose()a call throwsClosed, and what it had read ahead is not given.
Directories
std.fs (design 10 O5; M8b S7a, S7b): what a path names, the directories and
their entries, and what is put together from them. Each call of the system's runs on the I/O pool
(§Files rule 5).
fs.metadata(path)says what a path names, through links:sizein bytes,modified, anInstant,isFileandisDir— neither for what is neither —,readonly, andisSymlink, whether the path itself is a link. A link that leads nowhere is not found.fs.exists(path)is whethermetadataanswers: false where it would throw.fs.createDir(path)makes a directory in one that is there,fs.removeDir(path)removes an empty one,fs.remove(path)a file, andfs.rename(from, to)renames on one file system, a file thattonames replaced. What they cannot do is anIoErrorwith the path and what was done —made,removed,renamed to <to>,looked at—, as §Files rule 3 says. A directory that is not empty isOtherwith the system's code: no kind names it. What a directory removed as a file, or a file read as a directory, gives is the system's.fs.readDir(path)gives the directory's entries in the system's order, without.and.., asDirEntrys:name,path— the directory's joined with the name (§Paths) —, andisFile,isDirandisSymlinkof the entry itself, a link not followed; itsmetadata()follows it. ItsReadDirIter, withError = IoError, isCloseableand lets the directory go as a file's lines let their file go (§Files rule 7). A name that is no UTF-8 isInvalidDatawith the directory's path.fs.canonicalize(path)is the path absolute, its links resolved and its.and..taken out, as the system names it; it must be there.fs.absolute(path)makes it absolute by the working directory and normalizes it (§Paths), and asks nothing more of the file system.fs.createDirAll(path)makes the directory and every one above it that is not there; a directory there already is nothing to do, and something else there isNotDirectory.fs.removeAll(path)removes the path and all that is in it: a link is removed, never followed into, and nothing there is nothing to do. It removes what it can and throws the first error it met.fs.copy(from, to)copies a file's bytes —tomade or cut asFile.createmakes it — and answers how many. Onto itself, the same file bycanonicalize, it isInvalidInput, and from a directoryIsDirectory, before anything is written.fs.walk(path)gives the entries under the path, not the path itself: depth first, a directory before what is in it, each directory's entries in the byte order of their names, and links not followed. It reads each directory whole and lets it go, so it holds nothing open; itsErrorisIoError, and an error ends it.fs.tempDir()makes a new directory under the system's directory for temporary files and answers its path;fs.tempFile()makes a new empty file there and answers its path. Each name is made where nothing was, so it is the program's alone. Neither is removed by itself.
The console
std.io (design 10 O9; M8b S8a–S8c): the program's standard input, output and
error.
print(v)writesv's text — what itsDisplayshows — on the standard output, andprintln(v)the same followed by a newline;eprint(v)andeprintln(v)write on the standard error. Each takes one argument of any type that isDisplay; an f-string formats more.- Each stream is buffered: the output a line at a time where it is a terminal, a block at a
time otherwise; the error always a line at a time.
flush()writes what both hold, the output's first. What they hold is written at every end of the program: whenmainreturns, before a panic's report, and before an error that leavesmainis reported. These functions drop a failed write, but for the one rule 6 names. - (Informative.) Through one pipe the two streams show the modes: the error's lines come as they are written, the output's block at the end. A program another reads while it runs — a line someone waits for — flushes, as a C program does.
stdin()is the program's standard input, one for the program: aReaderwhose reads go through a buffer of its own, withreadLine()— the next line without its end,\nor\r\n, andnullat the end of the input — andlines(), an iterator of them withError = IoError. Areadtakes what a line's read left in the buffer first. The console's read runs on the I/O pool as a file's call does (§Files rule 5), soreadLine,readandlines()— aStdinLinesIterof its own — throwIoErroralone.stdout()andstderr()areWriters over the buffersprintandeprintwrite into, so what the two write keeps its order. Theirflush()throws what the system says — a pipe nobody reads isBrokenPipe; the console is notCloseable.- A standard output nobody reads ends a printing program: where the flush of
print,printlnorflush(), or the one whenmainreturns, finds the standard output a pipe nobody reads, the program ends as SIGPIPE would have ended it — on POSIX killed by that signal, which a shell shows as 141 —, what the error holds written first. AWriterofstdout()throwsBrokenPipeinstead (rule 5), and a failed write to the error is dropped. Where there is no such signal (Windows), the write is dropped and the program goes on; where a host embeds the program (13), the host's signals decide.
The terminal
std.term (design 10 Q7; M10 S10) is the terminal as ECMA-48 writes it: questions about the
standard streams, styles as SGR sequences, the cursor and the screen as sequences to print. No
raw mode (a door).
- The questions.
isTerminal(stream)forStdStream.Stdin,StdoutorStderr: whether the stream is a terminal (a pipe, a file or nothing is none).size(): the columns and rows of the terminal on the standard output,nullwhere it is none. Under Windows the runtime switches a console to virtual-terminal processing when it first writes to it, so the same bytes mean the same there. - Styles.
Style { fg: ?Color, bg: ?Color, bold, dim, italic, underline, inverse, strike }withColorthe 16 (Black…White,BrightBlack…BrightWhite),Index(n)of the 256 andRgb(r, g, b)(each clamped to 0..255).style.sgr()is the sequence that turns it on —ESC [the parameters in the order bold 1, dim 2, italic 3, underline 4, inverse 7, strike 9, the foreground (30–37, 90–97,38;5;n,38;2;r;g;b), the background (40–47, 100–107,48;5;n,48;2;r;g;b),m— empty for a plain style;reset()isESC [0m.style.apply (text)is the text between the sequence and the reset where styles are on, the text itself else;style.force(text)wraps whatever the terminal is; a plain style changes nothing. - When styles are on.
colorsEnabled():NO_COLORset and not empty says no; elseFORCE_COLORset and not empty says yes; elseTERM=dumbsays no; else the standard output being a terminal decides. An empty value counts as not set (std.ossets, it does not unset). - The cursor and the screen, each the sequence to print:
cursorUp(n = 1)ESC [nA,cursorDownB,cursorForwardC,cursorBackD,cursorNextLine(n)E,cursorTo(row, column)ESC [row;columnH(both from 1),cursorHide()ESC [?25l,cursorShow()ESC [?25h,cursorSave()ESC [s,cursorRestore()ESC [u,clearScreen()ESC [2J ESC [H,clearToEnd()ESC [0J,eraseLine()ESC [2Kand a carriage return,eraseToEndOfLine()ESC [0K.
The system
std.os (design 10 Q9; M8b S9): the program's arguments, its environment, its
working directory, its end, and the facts of the system it runs on. A text the system holds
that is no UTF-8 is taken with U+FFFD for its bad bytes, as string.fromUtf8Lossy takes it.
os.args()gives the program's arguments, its own name first, as a newstring[].os.env(name)is the variable's value, ornullwhere it is not set;os.envs()the whole environment as aMap<string, string>, a snapshot;os.setEnv(name, value)sets a variable for the process and those it starts. A name that is empty or holds=or a NUL, or a value that holds a NUL, is the program's mistake:setEnvpanics.os.cwd()is the working directory, absolute;os.setCwd(path)moves it. What they cannot do is anIoErrorwith the path.os.exit(code)ends the program withcode: what the console holds is written, and nodefer, nousingand no other task runs (06 §7).- The facts:
os.platform()(Platform:Linux,MacOS,Windows,Other) andos.arch()(Arch:X86_64,Aarch64,Other), whose texts are the target triples' words (linux,macos,windows,x86_64,aarch64) — the two kinds arestd.core's, whosetargetholds the build's (11 §3);os.homeDir(), the user's home ornull;os.tempDir(), the system's directory for temporary files, absolute (fs.tempDir()makes a new one in it);os.hostname();os.cpuCount(), one at least;os.pid().
The network
std.net (design 10 O7; M8b S10b, S10c): IP addresses, socket addresses,
TCP, UDP and the resolver. A call that would block parks the task until the thread's poller says the socket is ready,
and is a wait in the sense of 10 §1 rule 7: a cancel ends it with
Cancelled (§Input and output rule 2). There are no socket timeouts: withTimeout is the one
way. (Informative.) POSIX waits for its sockets through epoll or kqueue; Windows waits for its sockets through AFD
poll requests on the thread's completion port (M8b S11).
IpAddrisV4(a, b, c, d), fouruint8, orV6(…), eightuint16groups; it isEquatable.IpAddr.parse(s)takes version 4 as four decimal bytes, none with a leading zero, and version 6 as RFC 4291 §2.2 writes it — groups of one to four hex digits,::once for one or more zero groups, an IPv4 tail in the last 32 bits, no zone; anything else is aParseError. Its text is version 4 dotted, and version 6 as RFC 5952 §4 has it: lower case, no leading zero, the longest run of two or more zero groups — the first of equals — as::, and an IPv4-mapped address (::ffff:0:0/96) with its dotted tail.SocketAddr { ip, port: uint16 }isEquatable; its text, and whatSocketAddr.parsetakes, is192.0.2.1:8080or[2001:db8::1]:8080— a version 6 address in brackets, and only it —, the port decimal from 0 to 65535 with no leading zero.TcpListener.bind(addr)makes a listener; port 0 asks the system for a free one, whichlocalAddr()says.accept()waits until a connection comes and gives it as aTcpStream.TcpStream.connect(addr)waits until the connection is made; one that nobody listens for isConnectionRefused. A stream is aReaderand aWriter:readwaits until bytes come and answers 0 once the peer has shut its writing;writewaits until the system takes some. A peer that is gone isBrokenPipeorConnectionReset.shutdown(how),Shutdown.Read,.Writeor.Both, ends a way of the stream;setNoDelay(on)turns Nagle's waiting off;peerAddr()andlocalAddr()are its two ends.- A socket is closed once (
Closeable): afterclose()every call throwsClosed— a task that waits in a call of it, on any thread, runs again and throws it —, and a secondclose()does nothing. - (Informative.) Every wait is the task's own: its thread runs its other tasks meanwhile, so one thread serves many connections.
UdpSocket.bind(addr)makes a UDP socket, port 0 as for a listener.sendTo(from, addr)sendsfromas one datagram — whole or not at all — and waits until the system takes it;recvFrom(into)waits for the next datagram and answers how many of its bytes went intointo— the rest of a longer one is lost — and who sent it, aSocketAddr.resolve(host)gives the addresses a name stands for —localhost, a name the system's resolver knows, or an address's own text — each once, in the resolver's order, as aList<IpAddr>; a name nobody knows isNotFound. It runs on the I/O pool as a file's call does (§Files rule 5): no wait in the sense of 10 §1 rule 7.
URIs
std.uri (design 10 O11; M10 S5) is RFC 3986, strictly: Uri is a reference in its five
parts, Uri.parse reads one as the grammar allows, show writes it back, resolve and
normalize are the RFC's algorithms, Percent.encode/Percent.decode the encoding per
component, Query the pairs of a query. WHATWG's URL is a browser's leniency and not this.
- The parts.
Uri { scheme: ?string, userinfo: ?string, host: ?string, port: ?int, path: string, query: ?string, fragment: ?string }, eachnullwhere it was not written; the host stands without its brackets,""for an empty authority (file:///etc),nullfor none; a port written empty isnull.authority()is the text between//and the path,isAbsolute()whether there is a scheme.show()is §5.3: the parts recomposed, an address with a:in brackets —parseandshowround-trip every text the grammar takes. AUriisEquatable,Hashable,Debug,Display,ParseandCodable(its text, as anInstantis). Uri.parse(text)is §3 and appendix B: the scheme up to the first:where its characters are a scheme's and no/,?or#comes first; after//the authority up to the next/,?,#, split at the last@and the port's:, a host in brackets an IPv6 address (an IPvFuture is refused); the path up to?or#; the query up to#; the fragment. Each part is checked against its grammar:ParseError { kind = .Invalid }for a character a part does not take — a space, a control character, one beyond ASCII,<,>,",{,},\, a%without two hex digits, a port beyond 65535 or with a letter, a second@or#, a first segment holding:where there is no scheme and no authority (path-noscheme, §4.2). The empty text is the empty reference,Uri {}.base.resolve(ref)is §5.2.2, strict (a reference with the base's scheme is absolute, no backward compatibility): the reference's scheme or authority takes over, an empty path keeps the base's path and, where the reference has no query, its query, a relative path merges after the base's last/(at the root where the base has an authority and no path), dot segments go (§5.2.4;removeDotSegments(path)alone), the fragment is the reference's. The examples of §5.4.1 and §5.4.2 hold, each. A relative base gives a relative result (§5.2.1 asks for an absolute one; the algorithm does not need it).normalize()is §6.2.2 and §6.2.3: the scheme and the host in small letters, a percent-encoding of an unreserved character decoded, the others' digits in capitals, the dot segments of a path removed where there is a scheme or an authority, an empty path with an authority/, the scheme's default port dropped — 80 forhttpandws, 443 forhttpsandwss, 21 forftp.- The encoding.
Percent.encode(text, component)keeps what the component takes as it is and writes every other byte as%and two capital hex digits, a character beyond ASCII as the bytes of its UTF-8:Component.Userinfokeeps unreserved, sub-delims and:;Hostunreserved and sub-delims;Pathpchar and/;Segmentpchar (a/is encoded);QueryandFragmentpchar,/and?;Pairunreserved alone, for a key or a value of a query.Percent.decode(text)reads%XXback and isParseError { kind = .Invalid }for a%without two hex digits or bytes that are no UTF-8; a+stays.Queryis the pairs of a query in the order written, a key more than once allowed:Query.parse(text)splits at&and the first=(a part without=is a key with an empty value, an empty part is skipped), reads a+as a space and the percent-encodings;get(key)is the first value,all(key)every one,has,length,isEmpty,pairs();addappends,setreplaces the key's first pair and drops its others (appends where the key is new),removedrops the key;show()writeskey=valuejoined by&, each encoded forPair(a space is%20).uri.queryPairs()is the query parsed, empty where there is none.
HTTP
std.http (design 10 Q6; M10 S7 the client, S8 the server) is HTTP/1.1 over Reader and
Writer: the message types, a client that keeps its connections, a server with a task per connection. No TLS of its own (a
package's transport brings it), no HTTP/2, no cookies.
The message types.
Method(Get,Head,Post,Put,Delete,Patch,Options,Other(token);Method.of("GET"), itsshow()the token);Status { code, reason }withStatus.of(code)giving RFC 9110's phrase,isSuccess,isRedirect,isClientError,isServerError,isInformational, itsshow()404 Not Found;Headers, the fields in the order written, names compared without case:get(the first value),all,has,set(this value alone),add,remove,length,pairs.Request { method, uri, headers, body: ?Reader, contentLength: ?int },Request.get(uri),Request.post(uri, bytes, contentType),setBody(bytes),setText(text).Response { status, headers, body }withbytes(),text()(UTF-8, a wrong byte U+FFFD) andjson<T>()(std.jsoninto aDecodetype);bodyis aReaderthat ends where the message does.The client.
Client.new(options)withClientOptions { followRedirects = false, maxRedirects = 10, timeout = null, keepAlive = true, userAgent = "lyric5", transport = null };client.get(uri)andclient.send(request)give the response once its head is read. The request goes out as RFC 9112 writes it: the request target is the path (/for none) and the query,Hostthe authority (the port where it is not the scheme's),User-Agentwhere none is set, a body withContent-Lengthwhere its length is known and in chunks (Transfer-Encoding: chunked) else,Content-Length: 0for a POST, PUT or PATCH without one. The response's body is framed by the head: none for HEAD, 204 and 304;Transfer-Encoding: chunked(extensions ignored, trailers read away);Content-Length; up to the connection's close else (HTTP/1.0 among them). An interim response (100, 103) is read away. A connection is kept for the next request to the same scheme, host and port once the body has been read to its end, unless a side saidConnection: close, the body ran to the close, orkeepAliveis off — a body left unread gives the connection up; an idle connection the peer closed meanwhile is tried again on a new one (a request without a body).client.close()drops the idle ones.Redirects, time, transport. With
followRedirects, a 301, 302, 303, 307 or 308 with aLocationis followed, the target resolved against the request's URI (§URIs), up tomaxRedirects— more isHttpError { kind = .Redirects }; a 303, and a 301 or 302 after a POST, go on as a GET without the body; a 307 or 308 keep the method, and with a body are refused (it cannot be sent again).timeoutspans the whole exchange — the connection, the request, the head — and ends it withTimedOut, the connection closed.transportis afn(host, port) -> Stream(Stream :: [Reader, Writer, Closeable]; aTcpStreamis one) that makes the connection;httpconnects over TCP where it isnull,httpsneeds one. What is refused: a scheme that is neither (HttpError { kind = .Unsupported }), a URI without a host, a status line or header line that is not HTTP/1.1's (.Protocol: a version that is none, aContent-Lengththat is no number, a folded line, more than 256 lines or a line beyond 64 KiB), a body that ends early or a chunk that is none (IoError,UnexpectedEoforInvalidData). The errors of the connection areIoError, a cancelCancelled.http.get(text)is a GET with the defaults and redirects followed.The server (M10 S8).
Server.bind(addr, options)withServerOptions { keepAlive = true, serverName = "lyric5" },localAddr(),serve(handler)—handler: fn(Request) -> Response throws Error— andshutdown().serveaccepts until the listener is closed and takes a task per connection; on it each request is read as the client writes one: the request line (origin-form, absolute-form or*; HTTP/1.1 or 1.0), the headers (§3's limits), the body framed byContent-LengthorTransfer-Encoding: chunkedand given to the handler as the request'sbody(aReader;request.bytes(),text(),json<T>()read it whole; a body the handler leaves is read away before the next request).Expect: 100-continueis answered with100 Continuebefore the handler runs. The handler's response goes out asHTTP/1.1withServer,Content-Lengthwhere the body's length is known and in chunks else, nothing for a HEAD, a 204, a 304 or a 1xx;Response.of(status, bytes, contentType),Response.withText(text, status),Response.withJson(value, status),Response.stream(status, reader, length)andResponse.empty(status)make one, itsheadersare the handler's to set. A handler that throws answers500with the error's message as text. The connection stays for the next request unless the request was HTTP/1.0 withoutkeep-alive, a side saidConnection: close,keepAliveis off, or the server is shutting down; it is closed after the peer's remaining bytes are read away (a close with bytes unread would reset it and lose the answer). A request that is none — a request line or version that is none, noHostunder HTTP/1.1, a target that is no URI, aContent-Lengththat is no number, a transfer encoding that is not chunked, a folded line — is answered400 Bad Requestwith the reason as text and the connection closed.shutdown()closes the listener (servereturns), closes the connections waiting for a request, and lets a request under way be answered first.
Processes
std.process (design 10 O8, Q9; M8b S12a): a child started, its
streams, its end.
Command { program, args, cwd, env, stdin, stdout, stderr }is an options struct: the program a path or a name looked up onPATH;argsafter its name;cwdthe parent's wherenull;envpairs set over the parent's environment; each streamStdio.Inherit— the default —,Stdio.PipedorStdio.Null. A NUL in an argument or a variable, or a variable's name that is empty or holds=, isInvalidInput.spawn()starts the child. A program that is not there isNotFound, one that may not runPermissionDenied, the program as the path. Its piped streams arestdin: ?PipeWriterandstdout,stderr: ?PipeReader— aWriterandReaders whose calls wait as a socket's do (§The network), and that areCloseable; closingstdinis the end of the child's input.wait()closes the child'sstdin, then waits for its end — a wait a cancel ends — and answers anExitStatus:success(),code(): ?int— 0 to 255 on POSIX, any 32 bits on Windows —, andsignal(): ?Signal, the signal that ended it; its text isexit code 3orsignal 9. Every task that waits for one child, on any thread, sees its end.kill()ends the child — SIGKILL on POSIX; on Windows its code is then 1 —,signal(s)sends its(10 Q9),Unsupportedon Windows, which has no signals; neither touches a child that has ended.output()starts the child with no input and both other streams piped, takes the two to their ends at once and waits: anOutput { status, stdout, stderr }.status()starts it with the parent's streams and waits.- (Informative.) On POSIX a child's end comes as SIGCHLD, which the runtime keeps for itself (10 Q9). A child starts with SIGPIPE at its default although the program ignores it: an ignored signal would outlive the exec. On Windows (M8b S12b) a child gets its arguments on one command line, quoted as the C runtime reads them back, and inherits its three handles alone; its pipes' calls block, and run on the I/O pool as a file's do (§Files rule 5).
Characters
A char is a Unicode scalar value (design 10 S4, S5; M8c S1). What it is,
std.core answers from the Unicode Character Database, its version pinned:
std.string.unicodeVersion is "18.0.0" for Lyric 5.0.
category(): UnicodeCategoryis the character's general category (UAX #44). The enum's thirty variants are the standard's, in its order:UppercaseLetter,LowercaseLetter,TitlecaseLetter,ModifierLetter,OtherLetter,NonspacingMark,SpacingMark,EnclosingMark,DecimalNumber,LetterNumber,OtherNumber,ConnectorPunctuation,DashPunctuation,OpenPunctuation,ClosePunctuation,InitialPunctuation,FinalPunctuation,OtherPunctuation,MathSymbol,CurrencySymbol,ModifierSymbol,OtherSymbol,SpaceSeparator,LineSeparator,ParagraphSeparator,Control,Format,Surrogate,PrivateUse,Unassigned. Nocharis aSurrogate; a code point the database does not list isUnassigned.isXasks Unicode,isAsciiXASCII.isAlphais a letter, the categories Lu, Ll, Lt, Lm and Lo;isDigita decimal digit of any script, Nd;isAlphanumericeither;isUpperLu;isLowerLl;isControlCc;isWhitespacethe property White_Space.isAsciiDigitis0–9;isAsciiAlphaa–zandA–Z;isAsciiHexDigitthe digits anda–f,A–F;isAsciiWhitespacethe space, tab, line feed, form feed and carriage return.- Case maps one character to one.
toUpper()andtoLower()are the database's simple case mappings; a character without one maps to itself —'ß'.toUpper()is'ß'. No locale takes part. They, andtoDigit(rule 4), arestd.string's, a block oncharthe prelude passes on; what classifies a character (rules 1, 2) isstd.core's. - Digits are ASCII, as
parsereads them:toDigit(radix): ?intreads0–9, thena–zorA–Zas 10 to 35, and isnullfor a character that is no digit of the base;char.fromDigit(n, radix): ?charwrites the digitn, its letters lowercase, and isnullwherenis no digit of the base. A radix outside 2–36 panics (LYR-RT0008).
Strings
A string is immutable UTF-8 (design 10 S1). Its members stand once on StringView (below),
which a string reaches through a view of all of itself (rule 4); length() and isEmpty() it has
itself. What describes the text — length, isEmpty, charCount, chars, charIndices,
bytes — is std.core's; what searches in it, takes it apart or makes a new one of it is
std.string's, a block on StringView that the prelude passes on, so it stands in every module
(04 §3 rule 9a; design 10 P1–P5). What cuts gives a view of the text, no copy; what builds gives a
new string. A pattern p is a Pattern (design 10 S2) — a view or a string, a char, a test
of characters fn(char) -> bool (through the block on its function type, 05 §13
rule 6), or a type of the program that conforms:
Pattern { fn matchAt(text: StringView, at: int): ?int; fn matchBefore(text: StringView, end: int): ?int; },
the end of a match that begins at byte at, and the start of one that ends at byte end —
at and end a character's first byte or the text's length. A third member,
fn nextIn(text: StringView, from: int): ?int, the first such byte at or after from where a
match begins, has a default that tries matchAt at each; a pattern may give a faster one. One
signature per operation takes any of them:
length(), the bytes,O(1);isEmpty();charCount(), the characters,O(n);chars(), the characters decoded, anIteratorofcharthat isDoubleEnded—nextBack()takes them from the end, and the two ends meet;charIndices(), each character with the byte index it begins at, anIteratorof(int, char).stringisOrderedandTotalOrderbyte by byte, which is the order of the code points.- The search:
contains(p),startsWith(p),endsWith(p);find(p)andrfind(p), the byte index of the first and the lastpornull— an emptypstands at 0 and at the length;count(p), the occurrences none overlapping another, an emptypbetween every two characters and at both ends. split(sep), the parts between the separators in order, views, empty ones kept — a separator that matches the empty text panics;splitN(sep, n), at mostnparts, the last one the rest, none fornof 0;splitOnce(sep), the views before and after the first, ornull;findAll(p), the matches in order, views, none overlapping another;lines(), views split at\nwith a\rbefore it taken off and no empty line after a last\n.trim(),trimStart(),trimEnd(), the view without the white space — the characters with the property White_Space (§Characters);trimMatches(p),trimStartMatches(p),trimEndMatches(p), the view without the matches ofpat the ends, again and again — a match of the empty text takes nothing;isBlank(), whether it holds nothing but white space, the empty text too;stripPrefix(p)andstripSuffix(p), the view of the rest, ornullwherepis not there.toUpper(),toLower(), a new string, each character by its simple case mapping (§Characters rule 3):"straße".toUpper()is"STRAßE", and a final sigma is no context.bytes(), the bytes, a newuint8[]— a copy, since a string does not change (design 10 S12d);string.fromChars(chars), the text of an iterator's characters in order, throwing what the iterator throws;parse<T :: [Parse]>(),T.parse(s)withTfrom where the result goes —let n: int = try s.parse();.replace(p, with), a new string with everypreplaced, none overlapping — apthat matches the empty text panics;replaceN(p, with, n), the firstnreplaced, all of them wherenis negative;padStart(width, fill = ' ')andpadEnd(width, fill = ' '), a new string, the width in characters;repeat(n), the textntimes over — a negativenpanics.- Counted in characters (design 10 P7), each walking the bytes,
O(n)where indexing isO(1):substring(start, end), the view of the characters fromstartup to but not includingend— outside0 ≤ start ≤ end ≤ charCount()a panic;charAt(i), characteri— outside0 ≤ i < charCount()a panic;charIndex(byte), the characters before bytebyte, which a character begins at or which is the length — else a panic;isCharBoundary(byte), whether it is such a byte,falseoutside0…length(). findFrom(p, from), the byte index of the firstpat or after bytefromornull— afromthat is no such byte panics;firstOf(chars), the byte index of the first character that is one ofchars' ornull;splitWhitespace(), the runs of characters between white space (§Characters rule 2), views, no empty one;rsplitOnce(sep), the views before and after the lastsep, ornull.isAscii(), whether every character is below U+0080, the empty text too;equalsIgnoreCase(o)andcompareIgnoreCase(o), the two compared character by character, each by its simple lowercase mapping (§Characters rule 3) —"Straße"and"STRASSE"differ —, a text that ends first ordered first.- A forward search for a text pattern —
find,findFrom,contains,count,split,splitN,splitOnce,findAll,replace,replaceN— takes time linear in the text and the pattern together, whatever they hold (design 10 P11). - Bytes to text (design 10 S3):
string.fromUtf8(bytes), the text the bytes of aSlice<uint8>are, copied — bytes that are no well-formed UTF-8 (an overlong form, a surrogate, beyond U+10FFFF, a byte that begins nothing, a sequence cut short) throw aUtf8Error(std.core) whoseoffsetis the first byte that is not, as the data's fault (10 B3);string.fromUtf8Lossy(bytes), the text with each maximal part of a sequence that is not well-formed replaced by U+FFFD — the Unicode Standard's recommended practice (3.9). StringBuilder(std.string, a class):appendStr(s)of a string or a view,appendChar(c),append(v)of aDisplay,length()in bytes,clear(),toString()— in a buffer that doubles, no chain of concatenations.
StringView is the view of a string's bytes (design 10 S1, 03 A2), a primitive of std.core
visible without an import, as Slice<T> is (03 §5.2 rule 6):
- A view is a pointer into a string's bytes and a length, two words, copied by a binding; it reads the bytes and never writes them. There is no lifetime: the string lives as long as any view of it does (03 §5.2 rule 5).
s[a..b]on a string or a view is the view of the bytes fromaup to but not includingb, in the forms of 03 §5.2 rule 2 —s[a..=b], an open end,^nas a bound. The bounds satisfy0 ≤ a ≤ b ≤ length(), and neither falls inside a character — on a byte that continues one —, or the expression panics withLYR-RT0003, in every profile. A view of a view is a view of the same string.s[i]on a string or a view is the character that begins at bytei,ianintas an array's index is (03 §5.1): outside0 ≤ i < length(), or at a byte that continues a character, a panic (LYR-RT0003). Counted from the end a byte is rarely where a character begins:^nstands as a bound of a range only (LYR-SEM0114). The characters are read and never written through the index (LYR-SEM0019). A string and a view conform toIndex<int>withOutput = char(05 §12 rule 9), so a type parameter that asks for it takes them.- At a coercion site a string stands where a view is expected and gives a view of all of
itself; a view never stands where a string is expected (
LYR-SEM0001) —toString()copies its bytes into a string of their own. A receiver is such a site, as an array's is for the members written onSlice<T>(03 §5.2 rule 4): where the string's own blocks give no member of the name,s.m()is the view's member, called on a view of all ofs. A view matched against a string literal compares its bytes (09 §2 rule 3). - A view has
length(), its bytes, built in as a slice's is,isEmpty()andtoString(). It isEquatable,Hashable,OrderedandTotalOrderby its bytes, as a string is — it hashes as the string of its bytes —,Display, its text, andDebug, quoted.==,!=and the orderings between a string and a view compare the two as views:"abc"[1..] == "bc".
Debug of text (design 10 S14) writes a string, a view or a char as it would be written: between
" — a char between ' —, with \\, the quote, \n, \r, \t and \0 escaped, and a character of
the categories Cc, Cf, Cs, Co, Cn, Zl, Zp, or a space separator other than the space, as \u{…},
its scalar value in lowercase hex. A container's Debug and Display take it (§Collections),
so ["a\"b", "c"] shows as it is written.
Repetition (design 10 C7): s * n is the text s n times over — "" for n of 0 — and a
negative n panics with LYR-RT0007, as [x] * n does; n * s is refused, the one order of
03 §5.1.
Compression
std.compress (design 10 Q8; M10 S9) is DEFLATE (RFC 1951), zlib (RFC 1950) and gzip (RFC
1952), whole in Lyric: an inflater, a deflater at the levels 0 to 9, the two wrappings, each as a
stream adapter and whole in memory.
- The formats.
Gzip.compress(bytes, level = 6)andGzip.decompress(bytes),Zlib.*andDeflate.*(raw) over bytes in memory;GzipWriter<W :: [Writer]>,ZlibWriter,DeflateWriter(new(inner, level = 6);WriterandCloseable—closewrites the final block and the trailer and flushesinner, a secondclosedoes nothing, awriteafter it isIoError { kind = .Closed };flushends the block under way and writes an empty stored block so the bytes so far are whole) andGzipReader<R :: [Reader]>,ZlibReader,DeflateReader(new(inner);Reader, 0 at the stream's end;DeflateReader.bytesOut(),GzipWriter.bytesIn()). gzip's header is the ten bytes with no name, no time,OS255 andXFL2 at level 9 and 4 at level 1; its trailer CRC-32 and the size, both checked; a header with an extra field, a name, a comment or a header CRC is read over; members in a row read as one. zlib's header is78with the level'sFLEVEL(0 below 2, 1 below 6, 2 at 6, 3 above) and the check; its trailer the Adler-32, checked; a preset dictionary is refused. - The deflater. Level 0 stores (blocks of at most 65535 bytes, a final empty one); 1 the
fixed tables over a greedy LZ77; 2 and above dynamic tables (the code lengths from the block's
frequencies, at most 15 bits, 7 for the code length code; the lengths run-length coded) — a
block is written with the fixed tables or stored where that is smaller; 4 and above lazy
matching, the chains longer and the nice length higher with the level (zlib's table). The
window is 32 KiB, a block at most 16384 symbols. A level outside 0..9 is
CompressError; -1 is 6. What the deflater writes, zlib and the platform'sgzipread; what they write, the inflater reads (the artifact:programs/gzipfile.lyragainst .NET'sGZipStreamandgzip -d). - The inflater reads stored, fixed and dynamic blocks (an over-subscribed code, a block of
type 3, a stored length that does not match its complement, a distance before the start, a
length or distance code beyond the tables, a stream that ends inside a block are each
IoError { kind = .InvalidData }with aCompressErrorinside, itsdetailthe reason). It takes frominnerwhat it needs and, once a block could be the last, one byte at a time, so a wrapper reads its trailer where the stream ended. - In the client. With
ClientOptions.decompress(the default),std.http's client saysAccept-Encoding: gzip, deflateand decodes a response'sContent-Encoding: gzip(orx-gzip, ordeflateas zlib) as the body is read; the response's headers then loseContent-EncodingandContent-Length; the connection returns as the raw body ends.
Regular expressions
std.regex (design 10 S8, B1; M10 S6) is RE2's syntax matched in linear time: a pattern
is a tree, the tree a Thompson NFA, the NFA run by a Pike VM that carries the captures with
each thread — every character of the text meets each instruction at most once, so the time is
the text's length times the pattern's, whatever the pattern. What makes a backtracker's time
exponential is not here: no backreferences, no lookaround.
- The syntax (
Regex.new(pattern)): literals;.(no newline unlesss); classes[a-z],[^…](a]first and a-last are themselves),\d \w \sand their capitals (ASCII, as RE2:0-9,0-9A-Za-z_,\t \n \v \f \rand the space),[:alpha:]and the other POSIX names inside a class ([:^alpha:]negated),\pL,\p{Lu},\PLover the Unicode categories by their one- or two-letter names (no scripts); groups(…),(?:…),(?P<name>…)and(?<name>…); alternation|; the repetitions* + ? {n} {n,} {n,m}, each lazy with a?after it, a count at most 1000, a{that is no count a literal; the anchors^and$(the text's ends; the lines' underm),\A,\z,\b,\B(ASCII word characters); the escapes\n \t \r \f \v \a \0,\xHH,\x{H…}, and\before a punctuation mark; the flagsi,m,sas(?i)for the rest of the group,(?i:…)for a group,(?-i)off. A pattern outside this isRegexError { offset, detail }: a group or class not closed, an unmatched), a repetition of a repetition or of nothing, a range reversed, an escape that is none (a backreference\1among them), a flag that is none (a lookaround(?=among them), a name twice, a category or POSIX class that is none, a code point beyond U+10FFFF, a repetition beyond 1000. - The match. Alternation is leftmost-first as in Perl and RE2: of the matches starting at
the leftmost position the first alternative's wins, a greedy repetition takes the most and a
lazy one the least, so
a|abfindsainabandab|afindsab. AMatchisstartandend(byte offsets, as a string's indices are; a character beyond ASCII is one character to.and to a class),value(its text, also itsshow()),group(i)andspan(i)for the groups (0 the whole;nullfor one that took no part or does not exist — the last iteration's capture stands),named(name),groups(). The empty pattern matches the empty text at every position. - The verbs.
isMatch(text);find(text)the leftmost match with its groups,findFrom(text, at)from a byte offset;findAll(text)the matches in turn, left to right, none overlapping, an empty match right after a match skipped (as Go does);replace(text, template)the first andreplaceAllevery match replaced by the template —$1,${1},${name}the groups,$0the whole,$$a$, an unmatched group empty, a$before anything else itself;split(text)the pieces between the matches (Go'sSplitwith no limit: an empty text is one empty piece, a match at the start adds no empty piece before it).pattern(),groupCount(),groupNames(). - The time is linear.
(a*)*bover a hundred thousanda,(x+x+)+yover twenty thousandx,(a?){30}a{30}over thirtya: each ends in the time of one pass. (A lazy DFA forisMatchandfindwithout captures is a door the plan opens at Messpunkt 4; acomptime Regex.newis one of the macro line.)
Collections
std.collections holds the containers (design 10 B7); none is safe across threads (C11). An
element is whatever a container's bounds admit, an object included, and every slot holds its own
(M8a S16).
An array literal stands for a container where one is expected (03 §5.1 rule 2;
design 10 K4): List<T>, Deque<T>, Heap<T> and Set<T> are each a FromArrayLiteral<T>
of std.core — static fn fromLiteral(items: T[]): Self, the container of the elements, in
order — and Map<K, V> is one of (K, V), a later value of a key replacing the earlier one;
Set and Map with the default hasher. let s: Set<int> = [1, 2, 3];,
let m: Map<string, int> = [("a", 1)];.
List<T>(C3), a class: its elements in order, at the front of a buffer that doubles when full.new(),of(xs)(a copy of the array),from(it)(the values of an iterator, in order),withCapacity(n)— room fornelements before it grows, taken at the first push, since a list holds no value it was not given (10 N3; M8a S14); every container'swithCapacity(n)panics on a negativen.xs[i]andxs[i] = vthroughIndexSet<int>(05 §12 rule 9), outside0 ≤ i < length()a panic (LYR-RT0003), and from the end,xs[^1], throughLength;get(i),nullthere.push(v),pushAll(xs),pop()(nullfor none),insert(i, v),removeAt(i),removeWhere(p)(how many went; apthat throws ends it there — what it decided is done, the rest kept),clear()(the room stays),truncate(n),swap(i, j),toArray()(a copy),shrinkToFit()(the room cut down to the elements — a view taken before keeps the buffer it was taken of); of anEquatableelementcontains(x),remove(x)(whether there was one) anddedup()(runs of equal ones kept once).asSlice()is a view of the buffer, so the members of arrays and views reach the list — a sort writes it; a push past the room moves the list to a new buffer, and an older view keeps the older one. A list isIterablefront to back (10 I9): a change of its length while it is walked panics at the next pull (LYR-RT0016, as for a map, a set and a deque).Map<K :: [Hashable], V, H :: [Hasher] = DefaultHasher>(C4), a class: what it holds, in no order of its own, each key once.Map<K, V>.new()hashes with the process's key, so its order differs from run to run, andwithCapacity(n)is the same with room fornkeys before it grows;withHasher(seed)takes a hasher, which is a value the map copies fresh for every key —FixedHasher.new()hashes alike in every run;from(pairs), the pairs of a view in a map, a later value of a key replacing the earlier one.m[k]throughIndexSet<K>withOutput = ?V: the value underkornull;m[k] = vputsvthere, andm[k] = nulltakeskout.insert(k, v)answers the value it replaces ornull;get(k)the value ornull;containsKey(k);remove(k)the value taken out ornull;length(),isEmpty(),clear();getOr(k, d);getOrInsert(k, make),make()'s value inserted wherekhas none;update(k, f), whether there was one to replace;retain(p), how many went. Their functions may throw, and the call throws it with the map still a map:make()inserts nothing,freplaces nothing,retainkeeps what it has not judged. A map isIterableover its entries,(K, V),entries()too, andkeys()andvalues()walk the same order — none the map's own (I8); a change of the map while it is walked panics at the next pull (I9). (Informative.) A Swiss table: groups of eight slots under a control word, the hash's seven low bits compared a group at a time, at most seven of eight slots filled.Set<T :: [Hashable], H :: [Hasher] = DefaultHasher>(C5), a class over a map's keys:new(),of(xs)(the array's values, each once),withCapacity(n),withHasher(seed),insert(v)(whether it was not there),contains(v),remove(v)(whether it was there),length(),isEmpty(),clear();Iterableover its values, in no order of its own. Its operations are methods, no operators:union(o),intersect(o),difference(o)andsymmetricDifference(o), each a new set hashing as this one does;isSubset(o),isSuperset(o)andisDisjoint(o)— the empty set a subset of every set.- Every iterator's
toList()collects what it gives into aList(I4). Deque<T>(C6), a class over a ring buffer:new(),withCapacity(n),pushFront(v),pushBack(v),popFront()andpopBack()(the value ornull),peekFront(),peekBack(),length(),isEmpty();[i]from the front, panicking outside0 ≤ i < length()(LYR-RT0003),[^n]from the back throughLength;Iterablefront to back, a change while it is walked panicking at the next pull (I9).Heap<T :: [TotalOrder]>(C1), a binary heap over a list, the least value first (the review's A1; the greatest first is a heap of a type whose order is reversed):new(),withCapacity(n),of(xs)— the array's values, copied, in linear time —,push(v),pop()andpeek()(the value ornull),length(),isEmpty(). (Informative.)
Membership (10 C12; M8a S15): a List of an Equatable element is a Contains<T> through
contains(x), a Set through its own, a Map of its keys, contains(k) being containsKey(k) —
what x in xs asks (05 §12 rule 10). A Range<T> and a
RangeInclusive<T> of an Ordered T are Contains<T> too: start <= x && x < end, the
inclusive one with x <= end. A string and a StringView are Contains<char>: 'b' in "abc",
through the view's contains<P :: [Pattern]> at P = char (05 §1 rule 2) —
the same member that finds a text in a text.
Clone (10 K5; N2a). A List, a Deque, a Set, a Map and a Heap are Clone where their
elements — a map's keys and values — are: clone() is a new container of the elements' clones, as
deep as the element type owns; a map keeps its hasher, a heap and a deque their order.
Equality and display (10 C9; M8a S14). A List and a Deque are Equatable where their
elements are, element by element in order; a Set by its values and a Map by its entries —
the same keys, equal values, where V is Equatable — in any order. Each is Debug and
Display alike where its elements, a map's keys and values, are Debug: [1, 2] for a list
and a deque, {1, 2} for a set, {"a": 1} for a map, {} for an empty set or map, a set's and
a map's in the order they are walked. A Heap is Debug, its values in brackets in an order of
its own that no program may rely on. None is Hashable (10 C8).
A value that hashes is Hashable, fn hash<H :: [Hasher]>(&h: H): void, a child of
Equatable: it writes its parts into the hasher, and equal values write the same — nothing checks
it. An integer writes its word (writeInt, writeUint for the unsigned), a bool a byte, a
char its code point as a word, a string its bytes and then 255, which no UTF-8 text holds. A
float is none (10 SL-26); an array, a view, a List, a Map and a Set are none either (10
C8): their slots can be written. A type's own hash is synthesized (05 §14).
An object hashes by its address where its class is Identity (05 §14 rule
8): hashIdentityInto(o, &h) writes it as writeInt does.
Testing
std.test (design 10 B12 X1; M8a, the review's S12a) holds the
assertions a test fails by; lyric test runs a package's tests and subtest splits one
(14 §3). An assertion is a call like any other: outside a test it panics all the
same.
- An assertion that does not hold panics (
LYR-RT0008) with what it got against what was expected, each asDebugwrites it, the actual value first, and the expression the call wrote for it where that is not the value itself:assertEq(xs.length(), 3)of two elements saysxs.length() = 2, expected 3. Amessagethe call gives follows; two values too long to compare by eye get their first difference below — a text's first line that differs, else the first character. - The assertions:
assertEq(actual, expected)andassertNotEqover a type that isEquatableandDebug;assertTrue(c)andassertFalse(c);assertNull(v), andassertNotNull(v), which gives the value back;assertClose(actual, expected, tolerance)for floating point, NaN on either side failing;assertLessandassertGreaterover anOrderedtype;assertContains(c, x), whatx in casks;assertEmpty(c); andfail(message): never. assertThrows(f)fails unlessfthrows, and gives what it threw, of the typefsays it throws.assertPanics(f)runsfin a task of its own and fails unless it panics; it gives thePanicInfothe panic left — itscode,messageandtrace.
Benchmarks
A function marked @Bench is a benchmark (design 10 X4; M8c S12b): fn name(&b: Bench), in
a module under src/ or tests/, which lyric bench runs (14 §4). Bench is
std.core's, and in the prelude as Test is; the bench a benchmark is handed is its measure.
b.iter { … }runs the block until its time a run is known: once to warm up, then rounds of more runs until one takes a tenth of a second, then five rounds of that many. The bench keeps the round of the median time — itsruns, itsnanos, theallocationsthis thread made in it and theirallocatedBytes. The loop's call of the block is in the time.b.bytes(n)says that a run handlesnbytes; the report gives the megabytes a second.blackBox(x)ofstd.corehandsxback through a point the compiler cannot see through: what computed it is not dropped as unused, and the value that comes out is not known in advance.
Codecs
std.codec (design 10 B10 E1–E2, E5–E6; M10 S1) is serialization as a pair: Encode
writes a value into an encoder, Decode reads one out of a decoder, Codable is both. A
format — std.json, std.toml — is an encoder and a decoder of its own; std.codec holds the
pair, the model a format implements, the layer a conformance writes with, the errors, the
conformances of the core types, and Value, the tree of the forms that any format could write.
- The pair.
Encode { fn encode<E :: [Encoder]>(&e: E): void throws EncodeError; },Decode { static fn decode<D :: [Decoder]>(&d: D): Self throws DecodeError; },Codable :: [Encode, Decode]. A conformance is generic over the format's type, and the format is passed by reference, as a hasher is (§Hashing): the format keeps its state, and amutmethod of the layer hands&thison. - The model has ten forms, and a format implements them as primitives:
encodeNull,encodeBool,encodeInt,encodeUint,encodeFloat,encodeString,encodeBytes, andbeginSeq(n)/endSeq(),beginMap(n)/beginKey()/endMap(),beginStruct(name, n)/beginField(name)/endStruct(),beginVariant(enum, name, index, hasPayload)/endVariant(); the decoder mirrors them, pull-based:isNull()peeks,decodeInt()and the others take,beginSeq(): ?intanswers the count where the format knows it andseqHasNext()whether an element follows,beginMap()/mapHasNext()the same with a key and a value read in turn,beginStruct(name, fields)positions on a struct andstructField(name)on a field —falsewhere the data has none —,endStruct(denyUnknown)leaves it,beginVariant(enum, variants)answers the index of the variant the data holds andvariantHasPayload()whether one follows.path()says where the format stands, for an error. The sequence of calls is the contract: a format that cannot hold a form throws. - The layer is what a conformance writes with, on the format itself:
e.field(name, v),e.element(v),e.entry(k, v),e.encodeUnitVariant(…),e.encodeVariant(…, payload);d.field<T>(name)reads a field the data must have — a missing one isMissing—,d.optional<T>(name)one it may lack (nullthen, as for anullin the data),d.next<T>()the next element ornullat the end,d.nextKey<K>()andd.value<V>()a map's entry,d.decodeNullable<T>()a value that may be null. - The core types conform. The integers (a narrower one through
int, refused asInvalidwhere it does not fit; auintthe same),floatandfloat32,bool,string,char(a string of one character),Instant(its RFC 3339 text),?T(nullor the value),T[],Duration,Date,TimeandDateTime(their texts: as Go writes a duration, RFC 3339's full-date,HH:MM:SSwith the fraction, RFC 3339's date-time with its offset),Slice<T>(writes alone),List<T>,Deque<T>,Set<T>(a sequence, in the set's order),Map<K, V>(a map; the keys go to the format as values, which holds them or not), the tuples of two and three (a sequence of that length,Missingwhere shorter). Auint8[]is a sequence of numbers;Bytes { value }is the byte form the format holds its own way (design 10 E6: Base64 in JSON). A format reads a number as the form asked for: anintwhere auintfits, afloatfrom an integer. - The errors.
DecodeError { kind, path, detail }with the kindsMissing,TypeMismatch,Invalid,UnknownField,UnknownVariant; its message is the kind, the detail and the path —missing: field 'name' (at User),type mismatch: expected an int, got a string (at Team.[].User).EncodeError { path, detail, inner }. A format never panics on its input (design 10 B3). An unknown field passes unless the conformance ends its struct withendStruct(true). Valueis the tree:Null,Bool,Int,Uint,Float,String,Bytes,Seq,Map(entries in the order written),Struct(name, fields),Variant(enum, name, index, payload);EquatableandDebug.Value.of(v)is the tree a value writes,tree.read<T>()the value a tree reads as,Value.encoder()andtree.decoder()the two formats to pass on. AValueis itselfCodable: it writes its forms into any format, and reads the scalars and the two anonymous containers from one — a struct or a variant it cannot read without its name.Codablewrites itself (design 10 E3; 11 §4 rule 10): a struct or a class conforming without the members getsencodeanddecodeover its fields in declaration order — every field, whatever its word —, each under its wire name; a field the data lacks reads asdecodeAbsentgives it (nullfor an optional,Missingelse); an unknown field passes. An enum gets its variant: a unit one as the name, one payload field as the value, several as a sequence in their order; reading, an unknown name isUnknownVariant. The struct's name on the wire istypeName's. An ownencodeordecodereplaces the synthesized one (05 §14).@Codecsteers it (E4): on a field,namewrites it under that name andskipleaves it out — it reads as absent, so it is an optional or it isMissing—; on a struct or a class,rename = .SnakeCaseor.CamelCaserewrites every field's name anddenyUnknownrefuses a field the data has and the type has not (UnknownField); on an enum,tag = "kind"writes a variant as a struct of the tag and the payload's fields, named by position ("0","1"), and reads it by the tag.defaultis not read in 5.0: a field marked so reads as absent.
JSON
std.json (design 10 B10 E7; M10 S3) is RFC 8259, strictly: an encoder and a decoder of
§Codecs' model, and JsonValue, the tree.
- The text of a value.
json.encode<T :: [Encode]>(v, opts = JsonOptions { })writes anyEncodevalue as text;json.encodeTo(v, &w, opts)writes it into aWriter. A struct is an object of its wire names, a sequence an array, a map an object whose keys are strings — a number's digits where the key is a number; another key is anEncodeError—, bytes a Base64 string, a unit variant its name, a variant with a payload{"Name": payload};null,true/false, numbers asintor the shortest float text.NaNand the infinities have no JSON text (RFC 8259 §6): writing one is anEncodeError. The quote, the backslash and the control characters are escaped (\n,\t,\r,\u00XX), everything else stands as it is.JsonOptions:prettywrites newlines andindentspaces per level (default 2),sortKeysan object's keys in order. - The value of a text.
json.decode<T :: [Decode]>(text)reads anyDecodevalue; a text that is no JSON isJsonError { line, column, detail }, a value the type cannot read aDecodeErrorof §Codecs rule 5 whose path names the structs, the members and the indices —Team.members[0].User.id.json.decodeFrom<T>(&r)reads aReaderto its end first. A struct reads from an object by its members' names (the rest ignored unless the type denies them), a sequence from an array, a map from an object (a key as the number it spells where the key type is one), a variant from its name or from an object of one member, bytes from Base64. A number with a fraction or an exponent read as anintisInvalid; one beyondintis a float in the tree andInvalidas anint; a number read as afloatmay be an integer. - Strict. One value, whitespace (space, tab, CR, LF) around it, nothing else: a trailing
comma, a leading zero,
+,.5,1., an unknown escape, a lone surrogate, a control character unescaped, a text after the value are refused with the line and column. A member repeated keeps the last value. The document may nest 512 levels, its outermost value the first; one deeper is refused before what nests deeper is read, and a document the parser takes is compared, written and shown in any task (13 rule 2; the sweep after M10, W5). JsonValueis the tree:Null,Bool,Int,Float,String,Array(List),Object(Map);Equatableby content (an object's order aside),DisplayandDebugthe compact text.json.parse(text)reads a text into it;v["key"]is a member ornull,v.at(i)an element ornull,v.path("a.b[0]")walks both,asInt/asFloat(anIntwidened)/asString/asBool/asArray/asObject/isNullask. AJsonValueisCodableitself: it writes its forms into any format and reads any value's forms from one;v.read<T>()reads a value out of the tree,v.reader()is the decoder to pass on.
TOML
std.toml (design 10 B10 E8; M10 S4) is TOML 1.0: an encoder and a decoder of
§Codecs' model, and TomlValue, the tree. The toolchain's manifest reader is not
it (15 §1): std.toml is for programs.
- The document of a value.
toml.encode(v)writes anyEncodevalue whose form is a table — a struct, a map — as a document; another form is anEncodeError. A table's plain pairs come first, then its sub-tables under[a.b]headers (a table that holds sub-tables alone gets no header of its own), then its arrays of tables under[[a.b]]; a sequence is an array, a map a table whose keys are strings — a number's digits for a number; another key anEncodeError—, bytes an array of integers, a unit variant its name, a variant with a payload a table of one key (inline inside an array), a floatinf,-infornanwhere it is no number. A key is bare where it is[A-Za-z0-9_-]+, quoted else; a string is a basic string with the quote, the backslash, the control characters escaped. Anullfield is left out of its table (TOML has no null); anullelsewhere is anEncodeError.toml.encodeTo(v, &w)writes into aWriter. - The value of a document.
toml.decode<T>(text)reads anyDecodevalue out of a document; a text that is no TOML isTomlError { line, column, detail }, a value the type cannot read aDecodeErrorof §Codecs rule 5 whose path names the structs, the keys and the indices —Config.servers[0].Server.port. A struct reads from a table by its keys, a map from a table (a key as the number it spells where the key type is one), a sequence from an array, a variant from a string or a table of one key, bytes from an array of integers 0..255; a string reads from a string or from a date-time's text, so anInstantreads from an offset date-time; an absent key is absent (an optional readsnull). A float read as an integer isInvalid.toml.decodeFrom<T>(&r)reads aReaderto its end first. - TOML 1.0 (toml.io/en/v1.0.0): comments; bare, quoted and dotted keys; basic strings with
their escapes (
\b \t \n \f \r \" \\ \uXXXX \UXXXXXXXX), literal strings, both multi-line (the first newline dropped, a line-ending backslash dropping the whitespace up to the next character); integers decimal,0x,0o,0bwith underscores and without a leading zero; floats with fraction, exponent,inf,nan; booleans; the four date-times (offset, local, date, time, with a fraction andTor a space); arrays over lines with a trailing comma; inline tables on one line;[table]and[[array]]headers. Refused with line and column: a key defined twice, a table defined twice, a header for a table a dotted key made, a dotted key into a table a header or an inline table made, a header where a value stands, a header for an array of tables and the reverse, a trailing comma in an inline table, a leading zero, a bare underscore, a signed prefixed integer, a date that is none. The document may nest 128 levels: the root table the first, each segment of a header or a key one more, an array of tables one more for its element, an array's elements and an inline table's values one below them; one deeper is refused before what nests deeper is read. 128, not JSON's 512: aTomlValueis several times aJsonValue, and a document the parser takes is compared, written and shown in any task (13 rule 2; the sweep after M10, W5). TomlValueis the tree:String,Int,Float,Bool,Datetime(TomlDatetime),Array(List),Table(TomlTable);TomlTablekeeps its keys in the order written (get,set,keys,pairs,length);TomlDatetimeisOffset(DateTime),Local(Date, Time),LocalDate(Date)orLocalTime(Time), its text TOML's.Equatableby content,DisplayandDebuga table's document or a value's text.toml.parse(text)reads a document into a table;v["key"],v.at(i),v.path("a.b[0]"),asString/asInt/asFloat(anIntwidened)/asBool/asDatetime/asArray/asTableask. ATomlValueisCodableitself;v.read<T>()reads a value out of the tree,v.reader()is the decoder to pass on.
Syntax
std.syntax (design 10 B13 M2; M9b S1) is the syntax tree as values: what a program can
write, as enums and structs a comptime function reads, builds and writes back. The macros of
11 §5 take and give these values (M9b S2–S5).
- The tree is enums and structs,
Boxwhere a node holds its own kind.Expr,Stmt,Decl,TypeandPatternare enums, each form a variant, each node's last payload itsSpan;Ident { name, span }is a name,Literala literal with its suffix (Int(uint64, ?string),Float,Str,Bytes,Char,Bool,Null),Block { stmts, span }a block; a declaration's body is a struct —FnDecl,StructDecl,ClassDecl,EnumDecl,InterfaceDecl,ExtendDecl,LetDecl,FieldDecl,AssocType,AliasDecl,ImportDecl— as areParam,GenericParam,Attribute,Arg,FieldInit,WithField,Arm,Catch,LambdaParamandVariant;Bodyis an expression or a block,Nodeany of the five kinds, for a walk. ASpanis a file as the diagnostics name it, a line and a column from 1 —""and0for a node that was built, not read (Span.none()). The enums are@NonExhaustive(10 B13 M3): amatchover them outside the standard library writes a_, and a form the compiler has that the module has not — the rounds of acomptime forover fields (§4), a local function, a node the parser could not read — is refused where it is read (LYR-CT0005). Every node isDebug;Box<T>isDebugwhereTis, and writes its value alone. parseExpr(s),parseStmts(s)andparseDecls(s)read a text as the compiler reads it: an expression; statements as a block body holds them, a last expression without its;the tail; declarations as a module holds them. A text that is no syntax is a panic of the evaluation (LYR-CT0001), with the parser's message and the place in the text.toSource()writes a node as the formatter writes it — on anExpr, aStmt, aBlock(its statements, without the braces), aDecl, aTypeand aPattern. A tree read from a text comes back as the formatter would write that text, a built tree the same way: a literal from its value, so1.5e3is1500.0and0x10u8is16u8; no comments, since a tree holds none. The compiler's own pin: every program of its emitter tests goes tree, values, tree and gives the same text.children()gives the nodes directly inside one, in source order;walk(f)visits a node and every node below it, parents before children — on aNode, and on each of the five kinds.Ident.fresh(hint)is a name no written name is:hintand a number of its own, new at every call of an evaluation;Ident.of(name)is a name that stands nowhere. What aquotebinds is fresh (11 §5; M9b S3).- The tree lives at compile time. The functions of
std.syntaxthat read and write source run insidecomptimeonly; a call the program would make at run time is refused (LYR-CG0002), as the world is refused at compile time (11 §2 rule 3).