15. Core Library
The library ships under the toolchain's library root (§11 Library root): the modules of std/, and libraries with namespaces of their own (json, cli, regex, ...). Every exported declaration carries its documentation (§3 VaderDoc), and that documentation is the reference for the library.
This chapter covers the part of the library the language relies on: the prelude and the traits that syntax forms call, generators, and the functions that stop the program.
std/core#
// with Point and its Display of §9; no import: every trait used here comes from std/core
p :: Point { .x = 1, .y = 2 }
"at ${p}" // "at (1, 2)": interpolation calls to_string
"vader" + "lang" // "vaderlang": string implements Add
7 in 1..=10 // true: a Range<i32>, through Contains<i32>
std/core is the prelude: its exports are in scope in every module, with no import (§11 Prelude). It declares the eight integer types (§4 Primitive types) and the traits that syntax forms call:
| Form | Trait or type | Specified in |
|---|---|---|
a + b, a - b, a * b, a / b, a % b |
Add, Sub, Mul, Div, Rem |
§4 Operator overloading |
a == b, a != b |
Equals |
§4 Equality |
a < b, a <= b, a > b, a >= b |
Comparable |
§4 Operator overloading |
a[i], a[i] = v |
Index<I, T>, IndexSet<I, T> |
§4 Operator overloading |
x in c, x !in c |
Contains<T> |
§3 Membership: in |
lo..<hi, lo..=hi |
Range<T> |
Ranges, below |
for x in e |
Iterator<T>, Into<Iterator<T>> |
§7 for (universal loop) |
"${x}" |
Display |
§9 Display trait |
a conversion, implicit or T(x) |
Into<Target> |
§4 Type coercion via Into |
T | Error |
Error |
§10 Error trait |
await e |
Async<T>, Suspended |
§16 async/await |
@fields(T) |
Field |
§14 Reflection / comptime intrinsics |
It also declares:
Hash, a hash code:hash :: fn(self) -> u64, the same for two values equal byEquals. Every enum implements it (§4 Enums);MutableMapandMutableSetkey on it.Step, the next value of a discrete type:step :: fn(self) -> Self.Clone, an explicit copy:clone :: fn(self) -> Self.ZeroandOne, the additive and multiplicative identities, two static members (§4 Static members).FloatBits,BitsFloat,Float32BitsandBits32Float, the bits of a float (§4 Float bit patterns).ArrayOps<T>, the built-in methods of arrays:len,push,push_all,copy_to,remove_lastandclear(§4 Mutation methods).Continuation, the result of an iterator step (Iteration, below).- Functions on arrays:
filledandfilled_indexed(§4 Repeat / preallocate:[lhs] * n),content_equals,is_permutation_of,contains_allandis_disjoint(§4 Equality). - Functions on strings:
len,byte_len,byte_at,byte_slice,is_ascii,codepoint_at_byte,bytesandbytes_to_string(§4 Strings).
Implementations on primitive types#
[1, 2] + [3] // [1, 2, 3]: a new array
2 in [1, 2, 3] // true: T[] implements Contains<T>
'x'.step() // 'y'
(42).hash() // called like any other impl
i32.zero() // 0: a static member
remainder :: fn<T: Rem>(a: T, b: T) -> T = a % b
remainder(7, 2) // error: i32 does not implement Rem
"ell" in "hello" // error without import "std/string"
| Trait | Implemented by |
|---|---|
Display |
Every primitive, and null (§9 Display trait). |
Equals |
Every primitive: the integers, the floats, bool, char and string. |
Hash, Comparable |
The integers, char and string. |
Step |
The integers and char. |
Add, Sub, Mul, Div |
Every numeric primitive. |
Add |
Also string and T[], for concatenation. |
Zero, One |
Every numeric primitive. |
FloatBits, Float32Bits |
f64 and f32. |
BitsFloat, Bits32Float |
u64 and u32. |
Index |
string, at an i64 and at a Range<i64> (§4 Strings). |
Contains<T> |
Range<T>, and T[] when T: Equals. |
Mul<i64> |
T[], for [x] * n. |
Into<Iterator<T>> |
T[]. |
Into<string> |
Every type that implements Display. |
- No primitive implements
Rem:%on numbers is built in. a + bon two strings or two arrays is a new value,a's elements thenb's; neither operand changes.- Two equal strings have the same hash on every backend. The hash value is not otherwise specified.
- A primitive's impl is also reached through a bound
T: Hash(§4 Method dispatch on trait values). - The substring test
sub in scomes fromstring implements Contains<string>, instd/string, not in the prelude.
Ranges#
lo..<hi and lo..=hi build a Range<T> from lo up to hi, hi excluded or included. Over an integer type or char, a range is an Iterator<T> and a Contains<T>, through the Step and Comparable impls of T. A char range skips the surrogates. s in "a"..="m" is an error, since string has no Step.
Contains :: trait<T> {
contains :: fn(self, value: T) -> bool
}
Iteration#
Continuation :: enum(u8) { Continue, Stop }
Iterator :: trait<T> {
next :: fn(self) -> [T, Continuation]
}
next returns the next element and whether there was one. Since Continuation carries the second part, an iterator over a nullable type yields null as an ordinary element.
- When
nextreturnsStop, the element beside it means nothing, and no consumer may read it (for x in itnever does). An implementation returns anyTit has at hand. One that has none can be written as a generator (Generators, below). nextchanges the state of the iterator. Advancing a frozen iterator, such as a module constant, is an error (§5 Frozen values).- An implementation provides
nextand nothing else. What consumes an iterator (count,last,collect, ...) is a free function overIterator<T>, called with the method syntax:it.count().std/iterprovides the lazy combinators (map,filter,take, ...) and the terminals (collect,fold,sum, ...). - There is no
Iterabletrait. A type that can be iterated implementsInto<Iterator<T>>(§7for(universal loop)).
T[] implements Into<Iterator<T>>, so an array goes wherever an Iterator<T> is expected, as the receiver of a function over Iterator<T> too (§6 UFCS (Uniform Function Call Syntax)). Each conversion gives a fresh iterator, and an impl on the array's own type, such as i32[] implements Into<Iterator<i32>>, comes before the generic one (§4 Specialization: the instance wins). arr.map(f) returns an Iterator<U>, not an array; .collect() builds the array:
arr.map(f).filter(p).take(10).collect() // arr : T[] ; `arr.map(f)` is lazy, `.collect()` yields the T[]
for x in arr.filter(p) { ... } // a lazy chain also drives for-in directly
Generators#
A generator is a function whose body contains a yield. It returns an Iterator<T> and produces its elements one at a time, as they are asked for. No decorator marks it: the yield does.
concat_file_decls :: fn(files: SourceFile[]) -> Iterator<Decl> {
for f in files {
for d in f.program.decls { yield d } // flatten = a nested `for`
}
}
// consumed lazily: no intermediate array is built
for d in concat_file_decls(files) { process(d) }
- The declared return type must be
Iterator<T>,Tbeing the type of the yielded elements. A function that yields and returns another type is an error. - A function that returns
Iterator<T>without ayieldis an ordinary function that returns an iterator: an array,arr.into(), the result of a combinator. An empty array[]is an empty iterator. yield eis a statement, allowed directly in the generator's own body. Ayieldin a nested lambda or in adeferbody is an error.emust be assignable toT, as areturnvalue must be to the return type.return vin a generator is an error. A barereturn, or the end of the body, ends the iteration: every laternext()returnsStop.breakandcontinuekeep their meaning across ayield.deferin a generator is an error.- A generator's result is an ordinary
Iterator<T>value: it can be stored, passed, returned and given to any combinator.
There is no yield from or yield*: a nested for flattens, as in the example.
std/abort#
std/abort holds the functions that stop the program: panic(msg), todo(msg) and unreachable(msg). All three are @no_return (§12 @no_return), and the pending defer bodies run before the program stops (§7 defer). todo("lexer") stops with the message not yet implemented: lexer, and unreachable("lexer") with entered unreachable code: lexer. The module is not part of the prelude: a program that calls them imports it.