Download

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 by Equals. Every enum implements it (§4 Enums); MutableMap and MutableSet key on it.
  • Step, the next value of a discrete type: step :: fn(self) -> Self.
  • Clone, an explicit copy: clone :: fn(self) -> Self.
  • Zero and One, the additive and multiplicative identities, two static members (§4 Static members).
  • FloatBits, BitsFloat, Float32Bits and Bits32Float, the bits of a float (§4 Float bit patterns).
  • ArrayOps<T>, the built-in methods of arrays: len, push, push_all, copy_to, remove_last and clear (§4 Mutation methods).
  • Continuation, the result of an iterator step (Iteration, below).
  • Functions on arrays: filled and filled_indexed (§4 Repeat / preallocate: [lhs] * n), content_equals, is_permutation_of, contains_all and is_disjoint (§4 Equality).
  • Functions on strings: len, byte_len, byte_at, byte_slice, is_ascii, codepoint_at_byte, bytes and bytes_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 + b on two strings or two arrays is a new value, a's elements then b'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 s comes from string implements Contains<string>, in std/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 next returns Stop, the element beside it means nothing, and no consumer may read it (for x in it never does). An implementation returns any T it has at hand. One that has none can be written as a generator (Generators, below).
  • next changes the state of the iterator. Advancing a frozen iterator, such as a module constant, is an error (§5 Frozen values).
  • An implementation provides next and nothing else. What consumes an iterator (count, last, collect, ...) is a free function over Iterator<T>, called with the method syntax: it.count(). std/iter provides the lazy combinators (map, filter, take, ...) and the terminals (collect, fold, sum, ...).
  • There is no Iterable trait. A type that can be iterated implements Into<Iterator<T>> (§7 for (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>, T being the type of the yielded elements. A function that yields and returns another type is an error.
  • A function that returns Iterator<T> without a yield is an ordinary function that returns an iterator: an array, arr.into(), the result of a combinator. An empty array [] is an empty iterator.
  • yield e is a statement, allowed directly in the generator's own body. A yield in a nested lambda or in a defer body is an error. e must be assignable to T, as a return value must be to the return type.
  • return v in a generator is an error. A bare return, or the end of the body, ends the iteration: every later next() returns Stop.
  • break and continue keep their meaning across a yield.
  • defer in 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.