Standard library
Portable packages use the trb/std/* namespace. They resolve to
compiler-owned contracts and typed IR, so their argument types, results, and
runtime behavior remain consistent across Go, Ruby, and TypeScript.
Target-only functionality belongs to explicit trb/platform/<mode>/*
packages.
Output
puts is available in the small portable prelude and is the simplest way to
write a line:
puts("Hello, TypeRB!")
puts(1 + 2)
Import trb/std/io when declaration-root access is useful. Both forms resolve to the
same operation:
import trb/std/io
IO.puts("Hello, TypeRB!")
Scalars and strings
Portable built-in types expose receiver methods backed by the same contracts
as trb/std/numbers, trb/std/booleans, and trb/std/strings:
import trb/std/math
import trb/std/strings
text := 123.to_s()
ratio_text := 0.25.to_s()
ratio := 25.to_f()
whole := 2.75.to_i()
distance := (-8).abs()
bounded := 12.clamp(0, 10)
rounded := (-2.5).round()
root := Math.sqrt(9)
even := distance.even?()
ordinary := ratio.finite?()
enabled := true.to_s()
number := "123".to_i()
safe_number := "123".try_to_i()
decimal := "12.5".to_f()
safe_decimal := "+.5e1".try_to_f()
length := "Hello".size()
character := "A😀"[1]
last_character := "A😀"[-1]
safe_character := "A😀".try_fetch(2)
middle := "A😀BC".slice(1...3)
safe_middle := "A😀BC".try_slice(1..2)
first_emoji := "A😀B😀".index("😀")
last_emoji := "A😀B😀".rindex("😀")
characters := "A😀".chars()
reversed := "A😀".reverse()
replaced := "a😀a".replace_all("a", "$&")
upper := Strings.uppercase("Hello")
Integer parsing accepts a complete ASCII decimal integer with an optional sign.
to_i() raises on invalid or non-portable input; try_to_i() returns
Result<Integer, NumberParseError>. The error preserves its
NumberParseErrorKind, input, and message. Parsed integers use the portable
exact range -9007199254740991..9007199254740991.
Float parsing accepts a complete ASCII decimal with an optional sign, decimal
point, and exponent. At least one digit is required. to_f() raises on an
invalid spelling or a value that overflows the portable binary64 range;
try_to_f() returns Result<Float, NumberParseError>. Non-finite spellings
such as NaN and Infinity are not accepted. Values smaller than the binary64
range round to signed zero consistently across backends. Declaration-root
forms are Numbers.parse_float and Numbers.try_parse_float.
Integer#to_f() is an exact widening conversion. Float#to_i() truncates
toward zero and raises for non-finite or out-of-range values. Float#to_s()
uses a portable fixed decimal spelling without exponent notation, including
.0 for integral Float values. Their declaration-root forms are
Numbers.to_float, Numbers.truncate, and Numbers.float_to_string.
Integers provide abs(), zero?(), positive?(), negative?(), even?(),
odd?(), min(), max(), and clamp(). An invalid clamp range raises.
Floats provide abs(), floor(), ceil(), round(), truncate(),
finite?(), infinite?(), and nan?(); the integer conversions reject
non-finite and out-of-range results. round() resolves halfway values away
from zero. abs() converts negative zero to positive zero and leaves NaN as
NaN.
trb/std/math provides sqrt, exp, log, log2, and log10, returning
Float. Integer arguments use the ordinary safe widening to Float. Negative
square roots and logarithms are NaN, and logarithms of zero are negative
infinity. Exponentiation remains the ** operator.
Boolean#to_s() returns lowercase "true" or "false". Declaration-root
forms use Numbers.absolute, Numbers.zero, Numbers.positive,
Numbers.negative, Numbers.even, Numbers.odd,
Numbers.float_absolute, Numbers.finite, Numbers.infinite, and
Numbers.nan, plus Numbers.min, Numbers.max, Numbers.clamp,
Numbers.floor, Numbers.ceil, Numbers.round, Numbers.truncate, and
Booleans.to_string.
String#size, [], try_fetch, slice, try_slice, index, and rindex
operate on Unicode code points rather than encoded bytes. Indexes are
zero-based from the start; a negative index counts from the end, with -1
naming the final code point. value[index] is the sole strict single-element
form; it raises when the normalized index is outside the string, while
try_fetch() returns Result<String, IndexLookupError> and preserves the
requested index in an error. slice(range) accepts an inclusive .. or
exclusive ... Range<Integer> and raises for negative, reversed, or
out-of-bounds limits. try_slice() returns
Result<String, SliceRangeError> instead. An exclusive size...size range is
a valid empty slice. index() and rindex() search for literal substrings and
return a code-point offset as Integer?; an empty substring is found at zero
or at the string size, respectively. Additional receiver operations include
chars, codepoints, reverse, empty?, strip, lstrip, rstrip,
include?, start_with?, end_with?, split, replace_all, upcase, and
downcase.
chars() returns one String per code point, and reverse() reverses that same
sequence. String trimming uses the pinned Unicode 17.0 White_Space set,
preserves internal whitespace, and does not remove U+FEFF. replace_all()
replaces every non-overlapping literal occurrence. The replacement is also
literal, so strings such as $& and $1 have no special meaning; an empty
pattern raises. The declaration-root forms are Strings.characters,
Strings.reverse, and Strings.replace_all.
Bytes
Bytes is an immutable binary sequence distinct from String and
Array<Integer>:
import trb/std/bytes
payload := "A😀".to_bytes()
byte_length := payload.size()
first := payload.at(0)
text := Bytes.to_string(payload)
String conversion uses UTF-8. Decoding invalid input replaces it with U+FFFD;
call valid_utf8() first when invalid bytes must be rejected. Byte indexing is
strict and concatenation is non-mutating.
Binary encoding
trb/std/encoding/hex converts between Bytes and hexadecimal text:
import trb/std/encoding/hex
text := Hex.encode("A😀".to_bytes())
decoded := Hex.decode("41F09F9880")
Encoding uses lowercase ASCII. Decoding accepts uppercase or lowercase input
and returns Result<Bytes, Hex::DecodeError>. Invalid characters and odd-length
input are distinguished by Hex::DecodeErrorKind; the error also preserves the
input, a zero-based character position, and a message. For odd-length input,
the position is the missing character at the end of the string.
trb/std/encoding/base64 provides strict standard and URL-safe Base64:
import trb/std/encoding/base64
text := Base64.encode("A😀".to_bytes())
url_text := Base64.url_encode("???".to_bytes())
decoded := Base64.decode("QfCfmIA=")
url_decoded := Base64.url_decode("Pz8_")
encode() emits padded RFC 4648 Base64. url_encode() uses the URL-safe
alphabet without padding. Their matching decode functions accept only the
canonical form they emit and return Result<Bytes, Base64::DecodeError>.
Base64::DecodeErrorKind distinguishes invalid length, characters, padding, and
non-canonical trailing bits; the error includes the input, zero-based position,
and a message.
Hashing
trb/std/digest computes one-shot SHA-256 and SHA-512 digests. It also provides
SHA-1 and MD5 for legacy compatibility and non-security checksums:
import trb/std/digest
import trb/std/encoding/hex
digest := Digest.sha256("message".to_bytes())
text := Hex.encode(digest)
larger_digest := Digest.sha512("message".to_bytes())
legacy_digest := Digest.sha1("legacy identifier".to_bytes())
checksum := Digest.md5("legacy payload".to_bytes())
All four functions accept and return Bytes. Digest formatting stays explicit by
passing the result to hexadecimal or Base64 encoding. These synchronous APIs
have the same behavior in browsers, Bun, Node, Go, and Ruby.
SHA-1 and MD5 are not suitable for passwords, signatures, certificates, or other collision-resistant security decisions. Prefer SHA-256 or SHA-512 unless an external legacy format specifically requires the older digest.
trb/std/hmac provides HMAC-SHA-256, HMAC-SHA-512, and tag comparison:
import trb/std/hmac
def valid_tag?(key: Bytes, message: Bytes, expected: Bytes): Boolean
return HMAC.equal(HMAC.sha256(key, message), expected)
end
Keys, messages, and tags are Bytes. equal() compares equal-length tags
without content-dependent branching and returns false when their lengths
differ. Use it instead of ordinary collection comparison when verifying a tag.
Use trb/std/secure_compare for the same comparison contract outside HMAC:
import trb/std/secure_compare
matches := SecureCompare.equal(actual_digest, expected_digest)
equal() accepts two Bytes values. For equal-length values it examines every
byte without content-dependent branching. A length mismatch returns false;
the input lengths are not treated as secret.
Randomness
trb/std/random provides non-cryptographic random values for sampling,
simulation, and user-interface behavior:
import trb/std/random
fraction := Random.float()
index := Random.integer(10)
float() returns a Float in the half-open interval [0.0, 1.0).
integer(upper) returns an Integer in [0, upper) and fails at runtime when
upper is not positive. The generator and its sequence are backend-defined;
these functions must not be used for secrets, tokens, or other security
decisions.
Use trb/std/secure_random when bytes must be unpredictable:
import trb/std/secure_random
token := SecureRandom.bytes(32)
bytes(length) returns cryptographically secure Bytes. The portable
synchronous contract accepts lengths from 0 through 65,536 bytes so it can use
the browser Web Crypto source as well as native Go and Ruby sources. An invalid
length or unavailable secure source is a runtime failure; it never falls back
to the non-cryptographic generator.
StringBuilder and Unicode
Use StringBuilder for incremental text construction:
import trb/std/string_builder
mut builder := StringBuilder.new()
builder.append("Hello")
builder.append_codepoint(33)
blank := builder.empty?()
message := builder.to_s()
Destructive operations require a mut binding. to_s() returns an immutable
snapshot, and size counts Unicode code points.
trb/std/unicode provides scalar and identifier classification from one pinned
data set shared by all targets:
import trb/std/unicode
hiragana_a := 12354
is_letter := Unicode.letter(hiragana_a)
character := Unicode.from_codepoint(hiragana_a)
points := "A😀".codepoints()
The package also exposes digit, case, whitespace, valid-scalar, and identifier
classification. Unicode.version() reports the pinned data version.
Arrays and hashes
Collection contracts infer their type parameters from the receiver or
arguments. Array and Hash operations use receiver methods; there are no public
trb/std/arrays or trb/std/hashes packages:
mut values := [1, 2]
values.push(3)
first := values.shift()
values.unshift(0)
reversed := values.reverse()
ascending := values.sort()
descending := values.sort_descending()
ranked := ["second", "first"].sort_by do |label|
label.size()
end
deduplicated := [3, 1, 3, 2].uniq()
combined_values := values.concat([4, 5])
known := values.include?(2)
position := values.index(2)
occurrences := values.count(2)
has_even := values.any? do |value|
value % 2 == 0
end
all_positive := values.all? do |value|
value > 0
end
none_negative := values.none? do |value|
value < 0
end
first_even := values.find do |value|
value % 2 == 0
end
first_even_index := values.find_index do |value|
value % 2 == 0
end
inclusive_values := (1..3).to_a()
exclusive_values := (1...3).to_a()
mut labels: Hash<Integer, String> := {1 => "one"}
known_label := labels.key?(1)
label := labels.try_fetch(1)
keys := labels.keys()
combined := labels.merge({2 => "two"})
labels.update({2 => "two"})
labels.each do |key, value|
puts(key.to_s() + ": " + value)
end
removed := labels.delete(1)
Arrays provide size, emptiness, strict [], safe try_fetch, slice,
try_slice, first,
last, shallow dup, mutable push/unshift, mutable strict pop/shift,
non-destructive shallow reverse, stable non-destructive sorting, value
membership, first-position lookup with index(value), and occurrence counting.
Array<String> also provides join.
Range<Integer>#to_a() returns a new Array containing the same sequence that
Range iteration would visit. Inclusive and exclusive ends are honored; an
inclusive equal-bound Range contains one value, while exclusive equal-bound
and reversed Ranges are empty. The package form is ranges.to_array(range).
sort() and sort_descending() order an Array of Integer, Float, or
String. sort_by and sort_by_descending evaluate one non-fallible key
expression per element and accept the same key types. All four operations are
stable, including descending order. Strings use Unicode code point order,
independent of locale. Float NaN values sort after ordinary values in either
direction, and negative and positive zero compare as equal. Custom comparator
and locale-sensitive ordering APIs remain future design work.
uniq() returns the first occurrence of each element in input order and uses
the same portable equality contract as include? and count. concat(other)
returns a new shallow Array containing both inputs in order. Neither method
mutates its receiver; specifically, TypeRB concat() is not Ruby's destructive
Array#concat.
any?, all?, and none? evaluate a typed Boolean predicate from left to
right and stop as soon as the result is known. Empty Arrays return false for
any? and true for all? and none?.
find returns the first matching element as T?, while find_index returns
its position as Integer?. Both short-circuit and return nil when no element
matches. A nullable element type remains nullable, so finding a stored nil
and finding no element intentionally have the same result.
Value membership uses include?. index(value) returns the first matching
position as Integer?, and count(value) counts all matches. These operations
use portable == semantics and are available when the element type is numeric,
Boolean, String, or a payloadless enum. They do not inherit target-native
structural equality for Arrays, Hashes, records, or payload-bearing enums.
Hashes provide size, emptiness, strict fetch/delete, safe try_fetch, key
checks, keys, values, shallow dup/merge, and mutable update. merge
returns a new shallow Hash, while update mutates its receiver; duplicate keys
use the right-hand value. delete requires mut, returns the removed value,
and fails when the key is absent. Existing Hash arguments keep their exact key
and value types. each binds the key and value with their respective generic
types and traverses a shallow entry snapshot. Hash enumeration order is
unspecified.
Strict operations fail at runtime for missing keys, invalid indexes, ranges,
or empty edge removals. Array element access uses value[index]; safe fetch
returns Result<T, IndexLookupError> with the requested index and collection
size. Nonnegative indexes count from the start and negative indexes count from
the end, with -1 naming the last element. Array subsequences use
slice(range) and try_slice(range) with the same
range rules as String. The safe form returns
Result<Array<T>, SliceRangeError> and both forms return a new shallow Array.
Hash safe fetch returns
Result<V, KeyLookupError> with the missing String | Integer key. Both errors
also carry a stable message.
Array map, select, and reduce are structured language expressions rather
than target callbacks. See the
language guide for examples.
Array#concurrent_map is the import-free bounded I/O-concurrency form. It
returns block results in input order, uses a portable default limit of 8, and
accepts an explicit positive named limit. Nested calls share the current
structured task group's capacity. The operation does not imply CPU parallelism
or special-case Result; a Result-returning block produces an Array of Result
values. See the language guide and
decision record.
Logical paths
trb/std/path manipulates portable /-separated logical paths without
accessing the host filesystem:
import trb/std/path
config_path := Path.join("config", "../trbconfig.jsonc")
directory := Path.directory("src/compiler/main.trb")
parts := Path.components("/srv/type-rb")
The package provides clean, two-part join, absolute, components,
base, directory, and separator. Its behavior does not depend on the
target OS or current directory.
URL encoding
trb/std/url encodes individual URL components and ordered query parameters:
import trb/std/url
import trb/std/result
encoded := URL.encode_component("todos/日本語")
query := URL.build_query([
URL::QueryParameter.new(name: "tag", value: "type rb"),
URL::QueryParameter.new(name: "tag", value: "go"),
])
def decode_segment(value: String): Result<String, URL::DecodeError>
return URL.decode_component(value)
end
def decode_query(value: String): Result<Array<URL::QueryParameter>, URL::DecodeError>
return URL.parse_query(value)
end
encode_component preserves only RFC 3986 unreserved ASCII characters and
encodes all other UTF-8 bytes with uppercase hexadecimal escapes. It does not
encode spaces as +. decode_component likewise preserves a literal + and
returns Result<String, URL::DecodeError> for malformed escapes or decoded
bytes that are not valid UTF-8.
parse_query and build_query use an ordered Array<URL::QueryParameter> so
duplicate names and their global order are preserved. They accept and produce
query strings without a leading ?. Parsing treats + as a space, skips empty
& segments, and normalizes a name without = to an empty value. Building
uses + for spaces and percent-encodes reserved bytes. Invalid escapes and
decoded bytes that are not valid UTF-8 return URL::DecodeError.
Complete URL parsing remains a future addition to the package.
Filesystem
trb/std/filesystem is the explicit host-filesystem bridge. Every operation
returns a Result:
import trb/std/filesystem
import trb/std/result
def load_config(path: String): Result<String, FileSystem::Error>
return FileSystem.read_text(path)
end
The package provides existence checks, UTF-8 and raw-byte reads and writes,
recursive directory creation, and sorted immediate-child listing. Failures
carry operation, path, and message instead of exposing target exceptions.
Writes and directory creation return Result<Unit, FileSystem::Error>. Unit is a
storable value representing successful completion; it is distinct from the
internal Void return category.
Filesystem paths are host paths. Use trb/std/path separately for portable
lexical path operations.
Process
trb/std/process provides argument, environment, working-directory, and
shell-free process operations:
import trb/std/process
import trb/std/result
def run_formatter(files: Array<String>): Result<Process::Output, Process::Error>
return Process.run("formatter", files)
end
run takes a command and separate Array<String> arguments. It captures
stdout and stderr. A started process with any exit status is
Ok(Process::Output); launch or host failures are Err(Process::Error).
JSON and JSONC
trb/std/json provides an explicit JSON value enum plus typed record codecs:
import trb/std/json
import trb/std/result
record User
id: Integer @json("user_id")
name: String
nickname: String?
end
def decode_user(source: String): Result<User, JSON::Error>
return JSON.decode<User>(source)
end
def encode_user(user: User): Result<String, JSON::Error>
return JSON.encode(user)
end
Typed codecs support booleans, numbers, strings, nullable values, arrays,
Hash<String, V>, nested records, raw-value enums, and @json record-field
wire names. Raw-value enums encode as their declared String or Integer raw
value and reject unknown values during decode. Unknown object fields are
ignored. Schema information remains in typed IR; generated code does not
reflect over target objects.
JSON.parse accepts strict JSON. JSONC.parse additionally accepts line and
block comments. Both reject trailing commas. Parse, stringify, encode, decode,
and accessors return typed errors with JSON Pointer paths where applicable.
Go output currently uses the stable encoding/json API. The experimental
encoding/json/v2 package is not required by generated projects.
Date and time
trb/std/time separates exact instants from timezone-free civil values:
import { Date, DateTime, Duration, Instant, TimeZone } from trb/std/time
release_date := Date.parse("2026-08-11")
puts(release_date.to_s())
local_start := DateTime.parse("2026-08-11T09:30:00")
tokyo := TimeZone.get("Asia/Tokyo")
start := local_start.to_instant(tokyo)
finish := start.add(Duration.minutes(90))
puts(finish.to_datetime(tokyo).to_s())
The initial immutable types are Date, TimeOfDay, DateTime, Instant,
Duration, and TimeZone. DateTime is a civil value and has no implicit
timezone. Instant is an exact point and formats in UTC. Converting a local
DateTime through try_to_instant() reports nonexistent and ambiguous DST
times as DateTimeError; to_instant() is the strict convenience form.
Constructors and strict parse() reject invalid values. try_new(),
try_parse(), and TimeZone.try_get() return typed errors. Values support
component access, canonical to_s(), before?(), after?(), and same?().
Date adds whole civil days. Instant adds or subtracts fixed Duration
values and computes duration_since(). Instant.now() and package-level
now() read the current clock.
Canonical JSON codecs use strings: ISO dates and local date-times, RFC 3339
UTC instants, named timezone identifiers, and PT...S durations. The portable
range is Gregorian year 0001 through 9999 with nanosecond fields. A target or
database with lower storage precision may round-trip only the precision its
schema supports. Calendar periods, zoned date-time objects, locale formatting,
and injectable clocks are not part of the initial package.
Result and Unit
Import Result<T, E> explicitly:
import trb/std/result
Construct Result::Ok(value) or Result::Err(error) and handle it with an
exhaustive enum case. Prefix try unwraps Ok and returns Err from the
nearest compatible Result-returning function. Postfix catch unwraps Ok and
evaluates a local fallback or control transfer for Err. These constructs are
described in the language guide.
trb/std/unit provides Unit for successful generic operations with no
application payload, including Result<Unit, E> operations.
trb/std/errors provides EnumValueError for an unsuccessful raw-value enum
conversion. from_raw() infers this error type and its runtime dependency; an
explicit import is needed only when source code names EnumValueError itself.
Tests
trb/std/test exports describe, test, expect, expect_ok, and
expect_err. Suites and cases take literal names and parameterless blocks so
the compiler, CLI, language server, and editor use one deterministic discovery
model. expect() returns a typed Expectation<T> with equality, inequality,
Boolean, and nil assertions. expect_ok(Result<T, E>) returns T, while
expect_err(Result<T, E>) returns E; either helper fails the current case if
the Result has the opposite variant.
The package is portable compiler-owned source plus a small target runtime.
Assertion failures retain their original .trb path, line, and column. The
runtime protocol is consumed by trb test and editor integrations; application
code should use the public assertion API rather than its internal functions.
See the testing guide.
Package index
The current portable standard library includes:
trb/std/iotrb/std/stringstrb/std/numberstrb/std/mathtrb/std/booleanstrb/std/bytestrb/std/encoding/hextrb/std/encoding/base64trb/std/digesttrb/std/hmactrb/std/secure_comparetrb/std/string_buildertrb/std/unicodetrb/std/rangestrb/std/pathtrb/std/urltrb/std/filesystemtrb/std/processtrb/std/jsontrb/std/jsonctrb/std/timetrb/std/resulttrb/std/errorstrb/std/unittrb/std/test
Platform packages are mode checked and remain separate from the portable
standard library. Portable official application packages are documented in the
trb/http, trb/web, and
trb/orm guides.