Skip to main content

Standard library

The standard library is organized into pure modules under the std:: prefix. All functions are standalone — there are no methods on values.

Signatures use generic type parameters (A, B, T, R) to indicate where types are determined by the call site. The iteration context type is abbreviated as:

IterCtx = Object {
index: number,
size: number,
isFirst: bool,
isLast: bool,
}

std::collections

Generic collection operations. Most work on both lists and objects via automatic lifting.

map

map(collection: [A], callback: |A| -> B) -> [B]
map(collection: [A], callback: |A, IterCtx| -> B) -> [B]

Apply a function to every element:

use std::collections::{ map }

doubled = map([1, 2, 3], |x| x * 2) // [2, 4, 6]

The callback may accept an iteration context as a second argument:

indexed = map([`a`, `b`, `c`], |item, ctx| {
return { value: item, index: ctx.index }
})

filter

filter(collection: [A], predicate: |A| -> bool) -> [A]
filter(collection: [A], predicate: |A, IterCtx| -> bool) -> [A]

Keep only elements for which the predicate returns true:

use std::collections::{ filter }

evens = filter([1, 2, 3, 4], |x| x % 2 == 0) // [2, 4]

flatMap

flatMap(collection: [A], callback: |A| -> [B]) -> [B]
flatMap(collection: [A], callback: |A, IterCtx| -> [B]) -> [B]

Map then flatten one level:

use std::collections::{ flatMap }

expanded = flatMap([1, 2, 3], |x| [x, x * 10]) // [1, 10, 2, 20, 3, 30]

find

find(collection: [A], predicate: |A| -> bool) -> A | null
find(collection: [A], predicate: |A, IterCtx| -> bool) -> A | null

Return the first matching element, or null if none matches:

use std::collections::{ find }

first_even = find([1, 3, 4, 5], |x| x % 2 == 0) // 4
missing = find([1, 3, 5], |x| x % 2 == 0) // null

any

any(collection: [A], predicate: |A| -> bool) -> bool
any(collection: [A], predicate: |A, IterCtx| -> bool) -> bool

Return true if at least one element satisfies the predicate:

use std::collections::{ any }

has_large = any([1, 2, 100], |x| x > 50) // true

all

all(collection: [A], predicate: |A| -> bool) -> bool
all(collection: [A], predicate: |A, IterCtx| -> bool) -> bool

Return true if every element satisfies the predicate:

use std::collections::{ all }

all_pos = all([1, 2, 3], |x| x > 0) // true

count

count(collection: [A], predicate: |A| -> bool) -> number
count(collection: [A], predicate: |A, IterCtx| -> bool) -> number

Count elements satisfying a predicate:

use std::collections::{ count }

n = count([1, 2, 3, 4], |x| x % 2 == 0) // 2

length

length(value: string) -> number
length(value: [A]) -> number
length(value: Object { ...: A }) -> number

Return the size of a string, list, or object:

use std::collections::{ length }

string_len = length(`rank`) // 4
list_len = length([1, 2, 3]) // 3
object_len = length({ http: 80, https: 443 }) // 2

reduce

reduce(collection: [A], initial: R, step: |R, A| -> R) -> R
reduce(collection: [A], initial: R, step: |R, A, IterCtx| -> R) -> R

Left fold with an explicit initial accumulator. Returns initial unchanged when the list is empty:

use std::collections::{ reduce }

sum = reduce([1, 2, 3, 4], 0, |acc, x| acc + x) // 10

reverse

reverse(collection: [A]) -> [A]

Return a reversed copy of a list:

use std::collections::{ reverse }

reversed = reverse([1, 2, 3]) // [3, 2, 1]

zip

zip(left: [A], right: [B]) -> [Object { left: A, right: B }]

Pair elements from two equal-length lists by index. Lists must have the same length:

use std::collections::{ zip }

pairs = zip([1, 2, 3], [`a`, `b`, `c`])
// [{ left: 1, right: `a` }, { left: 2, right: `b` }, { left: 3, right: `c` }]

groupBy

groupBy(collection: [A], keyFn: |A| -> string) -> Object { ...: [A] }
groupBy(collection: [A], keyFn: |A, IterCtx| -> string) -> Object { ...: [A] }

Group elements by a string key. Groups appear in first-encounter order; elements within each group preserve input order:

use std::collections::{ groupBy }

grouped = groupBy([1, 2, 3, 4], |x| x % 2 == 0 ? `even` : `odd`)
// { odd: [1, 3], even: [2, 4] }

keyBy

keyBy(collection: [A], keyFn: |A| -> string) -> Object { ...: A }
keyBy(collection: [A], keyFn: |A, IterCtx| -> string) -> Object { ...: A }

Index a list into an object by a key function. Duplicate keys are errors:

use std::collections::{ keyBy }

indexed = keyBy([{ id: `a`, v: 1 }, { id: `b`, v: 2 }], |item| item.id)
// { a: { id: `a`, v: 1 }, b: { id: `b`, v: 2 } }

sort

sort(list: [number]) -> [number]
sort(list: [string]) -> [string]
sort(obj: Object { ...: T }) -> Object { ...: T }

Sort a list of numbers or strings in ascending order. Sort an object's fields lexicographically by key. Stable:

use std::collections::{ sort }

sorted_list = sort([3, 1, 2]) // [1, 2, 3]
sorted_obj = sort({ zebra: 1, alpha: 2 }) // { alpha: 2, zebra: 1 }

sortBy

sortBy(collection: [A], keyFn: |A| -> number | string) -> [A]
sortBy(collection: [A], keyFn: |A, IterCtx| -> number | string) -> [A]

Sort a list by a projected numeric or string key. Stable ascending order:

use std::collections::{ sortBy }

people = [{ name: `carol`, age: 30 }, { name: `alice`, age: 25 }]
by_name = sortBy(people, |p| p.name) // alice first

std::list

List-specific operations.

range

range(end: number) -> [number]
range(start: number, end: number) -> [number]
range(start: number, end: number, step: number) -> [number]

Produce a list of consecutive integers over the half-open interval [start, end). Single-argument form treats start as 0:

use std::list::{ range }

r = range(5) // [0, 1, 2, 3, 4]
s = range(2, 5) // [2, 3, 4]
t = range(0, 10, 2) // [0, 2, 4, 6, 8]

flatten

flatten(list: [[A]]) -> [A]

Flatten one level of nesting:

use std::list::{ flatten }

flat = flatten([[1, 2], [3, 4]]) // [1, 2, 3, 4]

slice

slice(list: [A], start: number) -> [A]
slice(list: [A], start: number, end: number) -> [A]

Extract a sub-list over [start, end). Negative indices count from the end. Out-of-range indices are clipped:

use std::list::{ slice }

s = slice([10, 20, 30, 40, 50], 1, 3) // [20, 30]
t = slice([10, 20, 30, 40, 50], -2) // [40, 50]

transpose

transpose(list: [[A]]) -> [[A]]

Transpose a list of equal-length lists (swap rows and columns):

use std::list::{ transpose }

t = transpose([[1, 2], [3, 4], [5, 6]]) // [[1, 3, 5], [2, 4, 6]]

std::object

Object-specific operations.

mapValues

mapValues(obj: Object { ...: A }, callback: |A, string| -> B) -> Object { ...: B }

Transform each value. The callback receives (value, key). Preserves field order:

use std::object::{ mapValues }

doubled = mapValues({ a: 1, b: 2 }, |value, key| value * 2) // { a: 2, b: 4 }

keys

keys(obj: Object { ...: T }) -> [string]
use std::object::{ keys }

ks = keys({ a: 1, b: 2 }) // [`a`, `b`]

values

values(obj: Object { ...: T }) -> [T]
use std::object::{ values }

vs = values({ a: 1, b: 2 }) // [1, 2]

entries

entries(obj: Object { ...: T }) -> [Object { key: string, value: T }]
use std::object::{ entries }

es = entries({ a: 1, b: 2 })
// [{ key: `a`, value: 1 }, { key: `b`, value: 2 }]

fromEntries

fromEntries(entries: [Object { key: string, value: T }]) -> Object { ...: T }

Build an object from a list of { key, value } pairs. Duplicate keys are errors:

use std::object::{ fromEntries }

obj = fromEntries([{ key: `host`, value: `localhost` }, { key: `port`, value: 8080 }])
// { host: `localhost`, port: 8080 }

std::string

String-specific normalization and search helpers.

trim

trim(text: string) -> string

Remove leading and trailing Unicode whitespace:

use std::string::{ trim }

trimmed = trim(` rank `) // `rank`

split

split(text: string, delimiter: string) -> [string]

Split a string on a literal non-empty delimiter. Empty segments are preserved:

use std::string::{ split }

parts = split(`api,worker`, `,`) // [`api`, `worker`]

contains

contains(text: string, needle: string) -> bool
use std::string::{ contains }

has_inner = contains(`rank`, `an`) // true

startsWith

startsWith(text: string, prefix: string) -> bool
use std::string::{ startsWith }

has_prefix = startsWith(`rank`, `ra`) // true

endsWith

endsWith(text: string, suffix: string) -> bool
use std::string::{ endsWith }

has_suffix = endsWith(`rank`, `nk`) // true

isBlank

isBlank(text: string) -> bool

Return true when the string is empty after trimming whitespace:

use std::string::{ isBlank }

blank = isBlank(` `) // true

This is a convenient validation helper for field constraints and declaration constraints:

use std::string::{ isBlank }

@constraint(cond: |self| !isBlank(self))
service_name = `api`

std::Regex

Regular expression operations. Regex literals use the re prefix: refoo-[0-9]+``.

matches

matches(text: string, pattern: Regex) -> bool
matches(text: string, pattern: Regex, options: Object { caseInsensitive?: bool, multiline?: bool, dotAll?: bool }) -> bool

Return true when the pattern matches the entire string:

use std::Regex::{ matches }

valid = matches(`v1.2.3`, re`v[0-9]+\.[0-9]+\.[0-9]+`) // true
search(text: string, pattern: Regex) -> bool
search(text: string, pattern: Regex, options: Object { caseInsensitive?: bool, multiline?: bool, dotAll?: bool }) -> bool

Return true when the pattern matches anywhere in the string:

use std::Regex::{ search }

found = search(`deploy ABC-123 today`, re`[A-Z]+-[0-9]+`) // true

replace

replace(text: string, pattern: Regex, replacement: string) -> string
replace(text: string, pattern: Regex, replacement: string, options: Object { caseInsensitive?: bool, multiline?: bool, dotAll?: bool }) -> string

Replace all non-overlapping matches. Replacement strings are literal — no $1-style back-references:

use std::Regex::{ replace }

normalized = replace(`ticket ABC-123`, re`[A-Z]+-[0-9]+`, `TICKET`)
// `ticket TICKET`

captures

captures(text: string, pattern: Regex) -> Object { full: string, groups: [string | null] } | null
captures(text: string, pattern: Regex, options: Object { caseInsensitive?: bool, multiline?: bool, dotAll?: bool }) -> Object { full: string, groups: [string | null] } | null

Return the first match with its capture groups, or null if no match:

use std::Regex::{ captures }

result = captures(`ticket ABC-123`, re`([A-Z]+)-([0-9]+)`)
// { full: `ABC-123`, groups: [`ABC`, `123`] }

std::Time

Pure time operations over structural Time::Instant and Time::Duration values. No implicit access to the host clock.

Time::Instant = Object { epochMillis: number, offsetMinutes: number }
Time::Duration = Object { millis: number }
Time::TimeZone = `UTC` | `preserveOffset`

Time::parse

Time::parse(text: string, options: Object { format: `RFC3339` }) -> Time::Instant
use std::Time

instant = Time::parse(`2024-01-15T12:00:00Z`, { format: `RFC3339` })

Time::format

Time::format(instant: Time::Instant, options: Object { format: `RFC3339`, timezone: Time::TimeZone }) -> string
rendered = Time::format(instant, { format: `RFC3339`, timezone: `UTC` })

Time::add

Time::add(instant: Time::Instant, duration: Time::Duration) -> Time::Instant
later = Time::add(instant, Time::minutes(15))

Time::diff

Time::diff(left: Time::Instant, right: Time::Instant) -> Time::Duration

Returns the duration left - right.

Duration constructors

Time::seconds(n: number) -> Time::Duration
Time::minutes(n: number) -> Time::Duration
Time::hours(n: number) -> Time::Duration

std::Path

Pure relative filesystem-style path operations.

Path::RelativePath // opaque path value

Path::from

Path::from(text: string) -> Path::RelativePath

Parse a forward-slash-separated relative path string. Rejects absolute paths and upward escapes:

use std::Path

p = Path::from(`configs/production.yaml`)

Path::join

Path::join(segments: [string]) -> Path::RelativePath

Construct a path from a list of single segments (no / allowed within a segment):

deployment = Path::join([`k8s`, `deployment.yaml`])

Path::toString

Path::toString(path: Path::RelativePath) -> string

Return the canonical forward-slash rendering:

label = Path::toString(Path::join([`k8s`, `service.yaml`]))
// `k8s/service.yaml`

std::Emit

Entrypoint-only emit descriptor constructors. std::Emit<T> is not directly emit-compatible inside ordinary objects or lists; the host realizes it when it is returned from ordinary pub main, and serve-time JSON/YAML responses may also realize a top-level std::Emit<T> body before serialization.

Emit::Format

Emit::Format = `json` | `yaml` | `text`

Emit::manifest

Emit::manifest<T>: (Object {
entries: [Object {
path: Path::RelativePath,
format: Emit::Format,
value: unknown,
}]
}) -> std::Emit<T>
use std::Emit
use std::Path

manifest = Emit::manifest({
entries: [
{ path: Path::join([`k8s`, `deploy.yaml`]), format: `yaml`, value: { name: `api` } },
{ path: Path::from(`README.md`), format: `text`, value: `# api\n` },
]
})

pub main = || manifest

Duplicate destination paths are errors. text entries require a string value; json and yaml entries accept any ordinary emit-compatible Rank value. Emit::manifest(...) is pure: it does not write to disk until a host command such as rank <entry> --file-root <dir> chooses to materialize it.


std::Rand

Seeded deterministic randomness. No ambient entropy — all randomness is derived from an explicit seed value.

Rand::Rng = Object { algorithm: string, state: string }
Rand::Draw<T> = Object { value: T, rng: Rand::Rng }

Every draw function returns both the sampled value and the successor RNG, so sequencing is explicit.

Rand::seed

Rand::seed(seed: number) -> Rand::Rng
use std::Rand

rng = Rand::seed(42)

Rand::derive

Rand::derive(rng: Rand::Rng, label: string) -> Rand::Rng

Derive a stable labeled sub-stream. Use this to create independent sequences that do not interfere with each other:

color_rng = Rand::derive(rng, `color`)
size_rng = Rand::derive(rng, `size`)

Rand::int

Rand::int(rng: Rand::Rng, range: Object { min: number, max: number }) -> Rand::Draw<number>

Draw an integer in the inclusive range [min, max]:

{ value: roll, rng: rng2 } = Rand::int(rng, { min: 1, max: 6 })

Rand::float

Rand::float(rng: Rand::Rng) -> Rand::Draw<number>
Rand::float(rng: Rand::Rng, range: Object { min: number, max: number }) -> Rand::Draw<number>

Draw a float in [0, 1), or in [min, max) with explicit bounds.

Rand::bool

Rand::bool(rng: Rand::Rng, options?: Object { probability?: number }) -> Rand::Draw<bool>

Draw a boolean. probability defaults to 0.5.

Rand::choose

Rand::choose(rng: Rand::Rng, list: [A]) -> Rand::Draw<A>

Pick one element uniformly at random. Error if list is empty.

Rand::shuffle

Rand::shuffle(rng: Rand::Rng, list: [A]) -> Rand::Draw<[A]>

Return a randomly permuted copy of the list.


std::types

Compile-time utility types for deriving new types from existing ones. Import them from std::types and use them only in type positions.

use std::types::{ Pick, Omit, Partial, Required, Record, Exclude, Extract, NonNullable, DeepPartial }

User = Object {
id: string,
name: string,
email?: string,
passwordHash: string,
}

Summary = Pick<User, `id` | `name`>
PublicUser = Omit<User, `passwordHash`>
Draft = Partial<User>
CompleteDraft = Required<Partial<User>>
Flags = Record<`featureA` | `featureB`, bool>
TextOnly = Extract<string | number, string>
NumberOnly = Exclude<string | number, string>
Email = NonNullable<User[`email`]>
UtilityEffect
Pick<T, K extends keyof T>Keep only fields K from object T
Omit<T, K extends keyof T>Remove fields K from object T
Partial<T>Make object fields optional
Required<T>Make object fields required
Record<K, V>Construct an object type with keys K and value type V
Exclude<A, B>Remove union members assignable to B from A
Extract<A, B>Keep union members assignable to B
NonNullable<T>Remove null from T
DeepPartial<T>Recursively make nested object fields optional

Pick and Omit reject keys outside keyof T at the alias boundary.

keyof and indexed access types such as User[profile] are language operators rather than std::types imports; see Values and types.


std::Decode

Decode<T>(text) converts boundary text into a typed value of T. The result is T | null: null means the text did not match T.

use std::Decode

Port = 8080 | 8443 | 9000

decoded_port = Decode<Port>(`8443`) ?? 8080

SysEnv = Object {
PORT?: Decode<Port>,
DEBUG?: Decode<bool>,
...: string,
}

env = Env<SysEnv> {}

Use Decode<T> when external systems supply strings but the rest of the program wants numbers, booleans, or literal unions. It keeps parsing at the boundary instead of normalizing raw strings later in the program.


std::Mutation

std::Mutation is the standard-library surface for provider-backed writes and commit-aware output projection.

Surface

use std::Mutation

Mutation::Effect<T>
Mutation::Receipt<R>
Mutation::Succeeded<Value, Receipt>
Mutation::Rejected
Mutation::Unknown
Mutation::NotStarted
Mutation::Result<Value, Receipt>
Mutation::CommitReport<Ops>

Mutation::plan(...)
Mutation::commit(...)

There is no separate std::Mutate module or function. The surface name is std::Mutation.

At a high level:

  • Mutation::Effect<T> declares a provider-backed write whose fulfilled payload has type T
  • Mutation::plan(...) collects labeled operations into one opaque plan value
  • Mutation::commit(...) turns that plan into a top-level std::Emit<T> descriptor
  • Mutation::Result<Value, Receipt> and Mutation::CommitReport<Ops> describe per-operation and aggregate commit outcomes

Use the std::Mutation names here as the raw surface reference. For the full lifecycle, examples, commit timing, result shapes, and constraints, see Mutations.


External input built-ins

These are part of the language, not importable modules.

Env<T> {}

Read environment variables from the host. The type parameter declares the expected schema. Only declared fields are materialized:

SysEnv = Object {
DATABASE_URL: string,
PORT?: string,
...: string,
}

env = Env<SysEnv> {}
port = env.PORT ?? `5432`

The environment is snapshotted exactly once per compilation.

File::Read<T> { path, format }

Read a structured file at a statically-known relative path:

Items = [Object { name: string, value: number }]

response = File::Read<Items> {
path: Path::from(`./data/items.json`),
format: `json`,
}

data = response.body

The result shape:

FieldTypeDescription
bodyTParsed, type-checked file contents
ctxObject { path: string, format: string }Metadata about the read

dotenv is also supported for .env-style flat text files:

  • the decoded shape is a flat string-keyed object
  • field-local Decode<U> and schema-wide File::Read<Decode<T>> use the same scalar-only decoding rules as Env<T> {}
  • interpolation and shell evaluation are intentionally not supported
use std::Decode

Config = Object {
PORT?: Decode<8080 | 9000>,
DEBUG?: Decode<bool>,
...: string,
}

response = File::Read<Config> {
path: Path::from(`./.env`),
format: `dotenv`,
}

port = response.body.PORT ?? 8080

Supported formats: json, yaml, csv, dotenv. CSV rows decode to header-keyed objects with string values. Dotenv rows decode to flat text fields with optional Decode<...> at the same boundary. path must be statically evaluable before any external input execution.

HTTP::Fetch<T> { url, method, headers?, cacheKey }

Make a network request and decode the response body against T:

use std::HTTP

Body = Object {
users: [Object { id: number, name: string }],
}

response = HTTP::Fetch<Body> {
url: `https://api.example.com/users`,
method: `GET`,
headers: {
Accept: `application/json`,
},
cacheKey: `users-v1`,
}

users = response.body.users
status = response.ctx.status

Request fields:

FieldRequiredDescription
urlyesRequest URL
methodyesHTTP method (GET, POST, etc.)
headersnoObject of header name → value strings
cacheKeyyesStable key for deterministic replay

The result shape:

FieldTypeDescription
bodyTParsed, type-checked response body
ctxsee belowRequest metadata

ctx fields:

FieldTypeDescription
statusnumberHTTP status code
cacheKeystringThe cacheKey from the request
headersObject { ...: string }Response headers

cacheKey is required for determinism — see HTTP snapshot caching for how to capture and replay responses offline.