Standard Library Overview
The compiler bundles standard.prelude. Other libraries are projects selected in
Dovetail.toml: add a matching standard-tag (or an explicit Git dependency), then
list project names in depends. Import declarations by their package names.
For example, project standard-json exposes package standard.json.
See Dependency Management for a full
manifest. The examples here state their dependencies. In an initialized project,
replace src/main.dove with the whole example and set root_package to its package
name. Each example includes a test and can be checked, built, run, and tested.
22.1 Core Types
Section titled “22.1 Core Types”The prelude source includes primitives, String, Option,
Result, List, Array, slices, BigInt, Decimal, and core traits such as
Equatable, Comparable, Hashable, and Display. These need no dependency or import.
Use Option<T> for ordinary absence and Result<T, E> for operations that can fail.
Private newtypes can validate data at construction. @derive(Equatable) opts a record
or enum into structural equality; equality is not automatically granted to every type.
Complete example (checked in CI) — no extra dependencies:
package core
newtype Quantity private = Int32
module Quantity = public function parse(value: Int32): Result<Quantity, String> = if value > 0 then Ok(Quantity(value)) else Error("quantity must be positive")
public function count(self): Int32 = self.value
function main(): Unit = assert Quantity.parse(3).require.count() == 3 assert Quantity.parse(0).isError
test "quantity rejects zero" = main()Here .require is appropriate in a test with a known value. In an application,
handle the error or propagate it with try/orReturn. See Error Handling.
22.2 Collections
Section titled “22.2 Collections”[1, 2, 3] creates an immutable List<Int32>. [|1, 2, 3|] creates a fixed-size,
mutable Array<Int32>. Slice<T> is a writable view of an array, and
ReadonlySlice<T> prevents writes through that view without freezing other aliases.
List length and indexed lookup traverse the list. Arrays provide indexed access
and a stored length. Choose arrays for bytes, mutation, and repeated random access;
lists are useful for immutable sequential processing.
The standard-collection project adds ArrayList,
MutableMap, MutableSet, MutableQueue, MutableStack, Range, and StringBuilder.
Several mutable collections currently require Default elements; maps require both
Default keys and values, plus Hashable and Equatable keys. Validated types may
not have a meaningful default. Check these bounds before choosing a collection.
Complete example (checked in CI) — depends = ["standard-collection"]:
package collections
import standard.collection.MutableMap
function main(): Unit = let counts = MutableMap<String, Int32>.new() counts.put("apples", 3) assert counts.get("apples").require == 3 assert counts.get("pears").isNone let values = [1, 2, 3].map((value: Int32) => value * 2) assert values.foldLeft(0, (sum: Int32, value: Int32) => sum + value) == 12 let array = values.toArray() array[0] = 9 assert values.head == 2
test "collections preserve the expected values" = main()Some existing collection constructors are named new; use the actual library API
even though new application APIs conventionally use make.
22.3 Text Processing
Section titled “22.3 Text Processing”String is UTF-8 text. Its prelude module provides interpolation, comparison,
searching, and conversion to bytes or characters. Concatenate strings with ++.
standard.text.Charset names encodings; currently it contains Utf8.
standard.encoding provides Base64 and Hex codecs with typed decoding errors.
standard.parser provides composable parsers; see Parser Composition.
Complete example (checked in CI) — depends = ["standard-encoding"]:
package encoding
import standard.encoding.Base64
function main(): Unit = let bytes = "hello".bytes() let encoded = Base64.encode(bytes) assert encoded == "aGVsbG8=" assert Base64.decode(encoded).require.readonly == bytes.readonly
test "base64 round trips bytes" = main()Byte chunks are not necessarily complete UTF-8 characters or complete protocol messages. Preserve incomplete input when implementing a streaming decoder.
22.4 Math
Section titled “22.4 Math”BigInt and Decimal are prelude types, with big and dec literal suffixes.
Decimal literals are constructed exactly from source digits. Both types support
addition, subtraction, multiplication, negation, equality, and ordering. Decimal
division and rounding operations are not provided yet. Normalization removes
insignificant trailing zeros; Decimal is not a fixed-display-scale money type.
The standard-math project currently provides
RoundingMode; the presence of that enum does not imply a Decimal
rounding API.
Complete example (checked in CI) — no extra dependencies:
package decimal
function main(): Unit = assert 0.1dec + 0.2dec == 0.3dec assert 19.99dec * 3dec == 59.97dec assert 12345678901234567890big + 1big == 12345678901234567891big
test "decimal arithmetic is exact" = main()22.5 I/O
Section titled “22.5 I/O”standard.io supplies Async<T, E>, Resource<T, E>, fibers, synchronization, and
console/byte-stream I/O. An async value describes work; constructing or discarding
it does not run it. Use await inside async code and .run() at the application
boundary. Acquire resources with use so their cleanup is scoped.
Complete example (checked in CI) — depends = ["standard-io"]:
package console
import standard.io.Asyncimport standard.io.Consoleimport standard.io.ConsoleError
async function greet(): Async<Unit, ConsoleError> = await Console.writeLine("Hello from async I/O!")
function main(): Unit = greet().run()
test "an async result can be consumed" = let program: Async<Unit, Never> = async do assert (await Async.succeed(42)) == 42 program.run()Console failures remain in the typed error channel. Console.writeLine is an async
operation; there is no synchronous Console.println. The prelude’s debug is a
separate diagnostic convenience.
standard.io.fs adds Path, file/directory operations, and file streams.
standard.io.log adds structured logging. dovetail run needs filesystem grants
for file access; see runtime permissions.
See Resource Management for release and cancellation rules.
22.6 Networking
Section titled “22.6 Networking”| Project | Package | Purpose |
|---|---|---|
standard-uri |
standard.uri |
URI representation and parsing |
standard-http |
standard.http |
HTTP/1.1 protocol codec without I/O |
standard-io-net |
standard.io.net |
Async TCP networking |
standard-io-http |
standard.io.http |
HTTP/HTTPS client, server, routing, buffered bodies |
standard-tls |
standard.tls |
TLS 1.3 protocol implementation |
standard-io-crypto |
standard.io.crypto |
Async TLS streams and managed crypto resources |
Use dovetail run <project> --allow-network for programs that open sockets or resolve
names. HTTP has basic routing; it is not a complete web framework. The HTTP/TLS
stack has outstanding hardening and conformance work. Read the
HTTP readiness inventory and the
TLS implementation boundaries before relying on it
for production workloads. Some older HTTP inventory entries lag the source; the
inventory is not a guarantee that a listed feature is absent or complete.
22.7 Time
Section titled “22.7 Time”standard.time provides values such as Duration, Instant, calendar dates, times,
and time zones. Constructing or comparing those values is pure; obtaining the
current time is I/O through the clock APIs. Pass time into business rules so tests
can choose it explicitly.
Complete example (checked in CI) — depends = ["standard-time"]:
package time
import standard.time.Durationimport standard.time.Instant
function main(): Unit = let start = Instant.ofEpochSecond(1700000000i64) let later = start.plus(Duration.ofSeconds(60i64)) assert later == Instant.ofEpochSecond(1700000060i64)
test "time arithmetic uses an explicit instant" = main()For deterministic tests, standard.io.TestClock supplies scoped virtual time to
the time extensions, Async.sleep, and timeout, with real-clock fallback outside
the installation. See Scoped Test Clocks.
22.8 JSON
Section titled “22.8 JSON”standard.json.Json is a JSON value tree. Json.parse returns a Result;
json.encode() serializes the tree. JsonEncoder and JsonDecoder support typed
conversion, and derives can generate implementations for public data shapes.
The decoder macro currently emits unqualified Json and JsonError names; import
both alongside the traits, as in the example.
Complete example (checked in CI) — depends = ["standard-json"]:
package json
import standard.json.Jsonimport standard.json.JsonEncoderimport standard.json.JsonDecoderimport standard.json.JsonError
@derive(Equatable)@derive(JsonEncoder)@derive(JsonDecoder)record Message = text: String identifier: Int64
function main(): Unit = let original = Message { text = "hello"; identifier = 9007199254740993i64 } let wire = original.toJson().encode() let decoded = Message.fromJson(Json.parse(wire).require).require assert decoded == original
test "a record round trips through JSON" = main()Json.Number contains a JsonNumber, which retains either an exact Int64 or a
finite Float64. Ordinary Int32/Int64 fields and derived codecs preserve every
valid integer through encoding and parsing; no application wrapper is needed.
Integer tokens are parsed directly from decimal digits with checked arithmetic.
An integer token outside Int64 fails during parsing with JsonError.OutOfRange("Int64"),
even in a nested collection. There is no BigInt or exact-decimal JSON storage.
Int32 decoding additionally checks its own range with OutOfRange("Int32").
Decimal points and exponents select floating-point storage. Integer decoders reject
all float nodes with JsonError.TypeError("Integer", "Float"), including 1.0,
1e3, 10e-1, and fractions whose binary64 approximation is integral. Quoted numbers
remain strings and are not coerced. This policy prevents prior floating-point rounding
from making an invalid integer input appear valid.
JsonNumber.fromInt32 and fromInt64 construct numbers exactly; toInt32 and
toInt64 return checked Results. JsonNumber.parse validates a complete numeric
token without whitespace. Construction of the JsonNumber enum is private: use
these helpers instead of constructing its Integer or Float variants directly.
JsonNumber.fromFloat64 rejects NaN/infinity with OutOfRange("finite Float64").
Float parsing rounds to binary64, preserves signed zero and subnormals, allows
underflow to signed zero, and rejects overflow to infinity with that same error.
Float64.fromJson and JsonNumber.toFloat64 accept both numeric categories;
converting large integers to Float64 can lose precision. Float serialization retains
a decimal point or exponent, so a whole-valued float stays a float after parsing.
Original numeric spelling is not preserved.
The infallible Float64.toJson() convenience encoder maps NaN/infinity to Json.Null,
also in derived fields. Use JsonNumber.fromFloat64 and propagate its Result when
non-finite values must fail explicitly.
Migration from 0.1.3: replace Json.Number(42.0) with
Json.Number(JsonNumber.fromFloat64(42.0).require) for a known finite float, or
42.toJson() for an integer. Code matching Json.Number(number) now receives
JsonNumber; use its conversion helpers. Integral floats no longer decode as
integers. JsonError.NonIntegralNumber remains as a legacy variant but is no longer
returned by the primitive decoders. Integer JSON text remains unquoted decimal.
Private construction is also respected: a generated decoder cannot bypass a private constructor. Decode a public input shape, then validate it through the private type’s module.
22.9 Randomness and Crypto
Section titled “22.9 Randomness and Crypto”The wasi project exposes host randomness through standard.wasi.random.
standard.crypto contains cryptographic primitives; standard.crypto.roots holds
trust anchors. standard.io.crypto supplies effectful random-key generation,
certificate loading, and resource-managed secrets.
Randomness and secret generation belong at the I/O boundary. Inject generated identifiers into domain logic. Consult the crypto design and implementation status for supported primitives and cleanup limitations; do not infer full protocol readiness from the existence of a primitive.
22.10 Streams and SQLite
Section titled “22.10 Streams and SQLite”standard-io-stream exposes standard.io.stream.Stream. It supports chunked sources,
transformations, sequential composition, concurrent merging, resource lifetimes,
and terminal consumers. See Streams for backpressure and ownership
rules. runCollect() retains the whole result; use a fold or output consumer for
large or unbounded sources.
standard-sqlite exposes standard.sqlite, bringing a prebuilt SQLite component
and its generated bindings transitively. Connections/statements are resources;
queries return async values. See the complete SQLite example
for parameters and transactions. The current SQLite build has filesystem-locking
limitations and no WAL mode. Postgres and Redis clients are not provided yet.
22.11 YAML
Section titled “22.11 YAML”Project standard-yaml exposes standard.yaml. It reads a documented subset of
YAML 1.2 Core into a source-located tree, then decodes that tree into application
types. Read files using the existing IO APIs and pass their text to Yaml.parse;
YAML parsing itself performs no IO.
Complete example (checked in CI) — depends = ["standard-yaml"]:
package yaml
import standard.yaml.Yamlimport standard.yaml.YamlDecoderimport standard.yaml.YamlError
@derive(YamlDecoder)record ServerConfig = host: String port: Int32 workers: Option<Int32>
function readConfig(text: String): Result<ServerConfig, YamlError> = let yaml = try Yaml.parse(text) ServerConfig.fromYaml(yaml)
function main(): Unit = let config = readConfig("host: localhost\nport: 8080\n").require assert config.host == "localhost" assert config.port == 8080 assert config.workers.isNone
test "read a typed YAML configuration" = main()Import Yaml and YamlError alongside YamlDecoder: generated implementations
use these names, just as the JSON derives use Json and JsonError.
Supported input
Section titled “Supported input”The reader supports nested block and flow mappings/sequences, compact sequence
entries, indentationless sequences as mapping values, plain and quoted strings,
YAML double-quoted escapes, comments, and multiline strings. Literal (|) and
folded (>) strings support chomping (+/-) and indentation indicators. Mapping
keys must be strings and fit on one line. Duplicate decoded keys are errors.
Unquoted scalars follow YAML 1.2 Core resolution: null, booleans, signed decimal
integers, unsigned hexadecimal/octal integers, and decimal/exponent floats,
including .inf and .nan spellings. yes, no, on, off, and dates remain
strings; quote numeric or boolean-looking mapping keys to make them strings.
Integers retain exact Int64 values and overflow is an error. Floats round to
Float64, including subnormals, signed zero, and overflow to infinity.
Integer decoders accept only integer nodes: decimal/exponent float spellings such
as 1.0 and 1e3 produce TypeMismatch, even when mathematically integral.
Int32 decoding checks its narrower range and returns OutOfRange("Int32").
Derived integer fields use these same checks and preserve exact Int64 values.
Leading zeros are decimal (012 is 12); +42 is accepted. Hexadecimal 0x and
octal 0o forms are unsigned and still limited to Int64.MAX. Signed radix forms,
0b binary syntax, and underscore separators remain strings, so integer decoding
fails with TypeMismatch. Quoted numeric text also remains a string.
A record decoded from YAML can use derived JSON codecs to persist Int64 fields
exactly; depend on both projects and import both sets of derive dependencies.
Yaml.parse accepts one document with optional --- and ... markers, an
optional leading BOM, and LF/CRLF line endings. Empty input produces null.
Nesting is limited to 128 collection levels. Anchors, aliases, explicit tags,
directives, complex/non-string keys, merge keys, and additional documents are
rejected. This is a YAML subset, not a complete YAML implementation. There is no
encoder or API for preserving comments and formatting.
Typed decoding and diagnostics
Section titled “Typed decoding and diagnostics”@derive(YamlDecoder) supports records, enums, transparent newtypes, and generic
types. Record decoders reject unknown fields. Missing required fields fail;
missing or null Option<T> fields decode to None. No field defaults are inferred.
The fromMissing() trait hook defaults to failure, while Option<T> supplies
None and transparent newtypes delegate to their inner decoder. This also works
when a generic field is instantiated with an optional type.
Built-in decoders cover Bool, String, Int32, Int64, Float64, Option<T>,
List<T>, Array<T>, tuples of arity two through six, and Yaml itself. Integer
narrowing checks range; floats do not silently truncate to integers. An integer
can decode as Float64, which may lose precision for large integer values.
Enums use the same shapes as the JSON derives: type names the variant, a single
positional payload goes in value, multiple positional payloads use a sequence
in value, and named payload fields appear alongside type. Unknown variants,
unknown fields, wrong payload lengths, and named payload fields conflicting with
type are rejected.
Yaml contains a YamlValue and optional YamlSpan; collections retain child
nodes and mappings retain key nodes. Yaml.make constructs a node without a source
location. For custom decoding, yaml.field<T>(name) applies missing-field policy
and adds error context; yaml.checkFields(names) enforces the allowed field set.
YamlError carries a structured kind, optional span, and field/index path.
error.format() renders messages such as
servers[2].port: expected Integer, got String at line 8, column 11.
Positions count Unicode scalars: offsets are zero-based and lines/columns are
one-based. Handle parse and decode errors as Result values at file boundaries.
Decode public input shapes before applying domain validation; derives do not
bypass private construction rules.
