core and the swappable packages
The standard library splits in two:
core— the only standard. The prelude surface, uniformly engine-implemented: it registers no host bodies and needs no mount beyond the base one.- the swappable packages — in-tree rut/host packages that a module
declares in its manifest
[deps](project structure). The community may replace any of them wholesale; nothing in the engine knows their names.
Anything else — gfx, imaging, your own my_map — is an embedder
package: a declaration file plus Rust bodies registered the same way
(embedding and native modules).
core — the ambient prelude
Every builtin name is in scope in every compilation unit; no use is
needed. A use core::{ … }; statement stays legal but is redundant. The
one exception is the const NAN: use core::{NAN} is explicit.
Functions
| signature | meaning |
|---|---|
assert(cond: bool, msg: str) -> nil | trap Assert when false; msg may be omitted at the call site |
panic(msg: str) -> nil | abort with the message |
on_drop<T>(p: ?T, cleanup: fn(?T)) -> nil | run cleanup(p) when p’s cell refcount reaches zero; one callback per pointer |
string_join(parts: [str]) -> str | join in one pass |
capture_stacktrace() -> StackTrace | opt-in stack snapshot: raw frames only, symbols resolved lazily per access |
str.from_code(n: u32) -> str | the 1-codepoint string |
Primitives and their members
builtin primitive str {
fn len(self) -> i32; // codepoints
fn code(self) -> u32; // the FIRST codepoint (traps on empty)
fn code_at(self, i: i32) -> u32; // traps out of bounds
fn encode(self) -> bytes; // the UTF-8 octets
fn slice(self, from: i32, to: i32) -> str; // O(1) view, codepoint bounds
fn scan(self, from: i32, set: [u8]) -> i64; // fused classify; (stop << 8) | class
fn starts_with(self, from: i32, head: str) -> bool;
}
builtin primitive bytes {
fn len(self) -> i32; // octets
fn decode(self) -> str; // UTF-8, lossy
fn clone(self) -> bytes; // the one copy escape hatch
}
// type-methods: bytes.zeroed(n) -> bytes, bytes.from(a: [u8]) -> bytes
builtin primitive opaque {
fn downcast<T>(o: Self) -> ?T; // nil on mismatch; `x is T` probes
}
Construction keeps its builtin forms: opaque(v) seals,
Weak.new(v) wraps (the class-method construction), StrBuf(cap)
pre-sizes (opaque, weak references).
Builtin classes
| class | members |
|---|---|
StackTrace | len() -> i32, name(i) -> str, line(i) -> i32, col(i) -> i32, render() -> str |
StrBuf | StrBuf(cap), push(str), push_code(u32) (invalid scalars mint U+FFFD), len() -> i32, finish() -> str — the ONE materialization; the builder keeps its buffer |
Weak<T> | Weak.new(v) (traps on nil; reference types only), upgrade() -> ?T — nil once the referent died |
Engine-woven traits
| trait | member | notes |
|---|---|---|
Iterator<E> | fn __iterate(self, emit: fn(E) -> bool) | for (x of it) desugars to it; emit returning false stops |
Future<T> | fn yield(cx: RunContext) | every async fn’s hidden frame implements it; await consumes it |
RunContext | checkpoint() -> u32, next_checkpoint(mut self, v: u32) -> nil, cancelled() -> bool | the async protocol’s cx record (async and await) |
Users implement these with ordinary impl blocks; the engine has
compiler-backed impls for its own types.
Numeric methods (per integer width i8–u64)
wrapping_add/sub/mul/shl — modular, no trap
saturating_add/sub/mul — clamped at the width's ends
checked_add/sub/mul — the tuple (value, ok); ok is false exactly
when the mathematical result escaped the width
Ambient like the primitives themselves: x.wrapping_add(y) needs no
use. The trap-on-overflow +/-/*/<< stay the default operators.
Constants
NAN (f64) — core’s one const, behind use core::{NAN}. All other float
constants are calc’s.
What core does not have
| removed | replacement |
|---|---|
Option<T> / Result<T, E> | ?T with nil as absence; (T, err) tuples (by-reference and nullable) |
own / make_ptr | ?T bindings are the cell reference |
char | str of one codepoint; s.code()/str.from_code(n) |
free downcast<T>(o) | opaque.downcast<T>(o) |
Array as a name | the [T] grammar; [v; n] repeat construction |
output builtins (print, console) | a logger package (ink) |
No removed surface keeps compatibility routing: a removed head in an unresolvable position is an ordinary unknown-name error.
The swappable set
| package | kind | surface |
|---|---|---|
rt | host pkg | create_logger(name: str) -> opaque, logger_log(log: opaque, level: i32, msg: str) |
ink | inline rut pkg | the Logger class over rt |
pouch | inline rut pkg | the growable sequence Vec<T> |
nmap_host / nmapset | host pkg + inline rut pkg | the native key table; HashMap/HashSet |
json | inline rut pkg, zero host fns | encodeJson / decodeJson / decodeJsonBytes + traits |
strbuild | inline rut pkg, zero deps | the StringBuilder class |
calc | host pkg | the Math namespace |
async_engine / async_host | host pkg + inline rut pkg | the launcher rows; launch_future / sleep |
http_host / http | host pkg + rut pkg | the std HTTP lanes |
bench-cross | host pkg | the crossing-tax benchmark rows |
rt and ink — logging
There is no print, no global output builtin. All logging goes through a
used logger; the host owns the sink, and an uninstalled sink is a silent
no-op — a script cannot accidentally spam an embedded host’s stdout.
use ink::{ Logger };
pub fn main() {
let log = Logger.new("app");
log.info(f"started");
}
started
| method | level passed to rt |
|---|---|
debug(msg) | 0 |
info(msg) / log(msg) | 1 |
warn(msg) | 2 |
error(msg) | 3 |
Embedder side: rut_std::logger::install_std_log(&mut hosts, |s| println!("{s}")).
Mounting ink pulls rt along ([deps]).
pouch — Vec<T>
The growable sequence, written in rut over the fixed [T] array:
| member | meaning |
|---|---|
new() / with_capacity(cap) / filled(v, n) | construction ([v; n] under the hood) |
push(v) / pop() -> T | pop returns the removed element; traps on empty — guard with len() > 0 |
get/set via v[i], for (x of v) | compiler-lowered; element access aliases the stored cell |
from([T]) / as_array() -> [T] | bridges to the fixed array |
freeze() -> bytes | the Vec<u8> → immutable bytes copy (exactly the live length) |
slice(from, to) -> ?Vec<T> | compiler-lowered O(1) fixed-length window; detaches on grow |
nmapset — HashMap/HashSet
Thin wrappers over the native key table (nmap_host rows): the whole
state is one opaque handle, every method one host call.
pub class HashMap<K requires i8 | i16 | i32 | i64 | u8 | u16 | u32 | u64 | bool | str | bytes, V> {
pub fn new() -> Self;
pub fn with_capacity(n: i32) -> Self;
pub fn put(mut self, k: K, v: V) -> bool; // true = newly inserted
pub fn get(self, k: K) -> ?V; // the STORED cell, not a copy
pub fn has(self, k: K) -> bool;
pub fn remove(mut self, k: K) -> bool;
pub fn len(self) -> i32;
}
pub class HashSet<T requires ..same key set..> { new, with_capacity, put, has, remove, len }
Laws: the key set is closed (no floats — no stable equality; encode a
custom key canonically to bytes); get answers the stored cell, so two
gets of one key name one value until a replace; values release host-side.
str keys hash by content; str keys also carry *_range(parent, off, len) members keyed by a byte range of the parent string — a range key is
the same key as its content, with no key cell minted on a probe
(string views).
calc — the Math namespace
use calc::{ Math };
use ink::{ Logger };
pub fn main() {
let log = Logger.new("t");
let x = 3.0f64;
let y = 4.0f64;
let d = Math.sqrt(x * x + y * y);
let a = Math.abs(x);
let hf = Math.sqrt_f(2.0); // f32 twin — the width is in the name
log.info(f"{d} {a} {hf}");
}
5 3 1.4142135
Functions (each with an _f f32 twin): sqrt, floor, ceil, round,
trunc, exp, ln, log2, log10, sin, cos, tan, asin,
acos, atan, sinh, cosh, tanh, pow, atan2, hypot,
copysign, fma, plus the float helpers abs, min, max, signum.
Constants: Math.PI, TAU, E, SQRT_2, LN_2, LN_10, LOG2_E,
LOG10_E, INFINITY, NEG_INFINITY, EPSILON, MAX, MIN,
MIN_POSITIVE. Integer numeric methods are core’s, not calc’s.
json — the serde package
Pure rut, inline = true, zero host fns — a json mount adds no host
bindings.
fn encodeJson<T requires JsonSerialize>(v: T) -> (?str, ?EncodeJsonError);
fn decodeJson<T requires JsonDeserialize>(s: str) -> (?T, ?DecodeJsonError);
fn decodeJsonBytes<T requires JsonDeserialize>(b: bytes) -> (?T, ?DecodeJsonError);
trait JsonSerialize { fn encode(self, mut w: JsonWriter) -> ?EncodeJsonError; }
trait JsonDeserialize { fn decode(mut r: JsonReader) -> (?Self, ?DecodeJsonError); }
- The pair law:
(?T, ?E)with exactly one nil — success =(value, nil), failure =(nil, err). Encode returns?EncodeJsonErrorbecause a cyclic structure is expected, recoverable data, not a trap. - Direct decode: no intermediate document; a type’s
decodereads its expectations straight off the cursor. AVec<Row>decode mints exactly the program’s values, once. - Numbers:
i64accepts integer lexemes only (overflow/decimal lexeme =WrongType, never a silent wrap);f64parses IEEE-exact in the common range, ±1 ulp beyond (disclosed); encode renders the shortest round-trip decimal. - Depth: capped at 128 both directions, recoverable
(
DecodeErrorKind::Depth/EncodeErrorKind::Depth). - Errors: payloadless kind enums + fixed-field structs —
DecodeErrorKind { Unexpected, Truncated, InvalidUtf8, WrongType, Depth, Trailing },EncodeErrorKind { Depth, NotFinite, KeyUnsupported }; decode details carryat/got/expected, encode details carryatand the lazily built$.rows[3].namepath. decodeJsonBytesis strict UTF-8 (InvalidUtf8), never lossy.- Ownership: json owns the traits; all container impls live in json,
gated by peer groups —
Vecrows activate whenpouchis anywhere in the consumer’s closure, the map/set rows whennmapsetis (dependency kinds). A consumer without the peers mounts json light. - The writer accumulates through
strbuild’sStringBuilder; the reader rides the corestr.scan/starts_withprimitives and O(1) string views.
strbuild — the builder
use ink::{ Logger };
use strbuild::{ StringBuilder };
pub fn main() {
let log = Logger.new("t");
let k = "name";
let mut b = StringBuilder.with_cap(1024); // octet hint
b.append(f"{k}=");
b.append_code(0x21);
let s = b.build(); // the ONE materialization
log.info(s);
}
name=!
| member | meaning |
|---|---|
new() | grow from small |
with_cap(cap: i32) | pre-size to an octet hint (negative traps) |
append(mut self, value: str) | amortized O(|s|), in place |
append_code(mut self, cp: u32) | one codepoint; invalid scalars mint U+FFFD |
len() -> i32 | codepoints so far, O(1) |
build() -> str | fresh immutable str; the builder keeps its buffer |
Sharing is the default: appends through an alias (or a mut parameter)
land in the caller’s document; copies happen at exactly two engineered
points — build’s materialization and growth’s prefix move.
calc’s company: async and http
-
async_enginedeclares the engine rows (__launch,__abort,__sleep,__sleep_yield);async_hostrestores the typed surface:launch_future(f: Future<T>) -> LaunchedFutureHandle<T>,LaunchedFutureHandle.abort() -> bool,sleep(ms: u32) -> Future<nil>. Each embedder mounts the pair and installsrut_std::async_host::install_std_async; a session that mounts neither has no launcher (tasks, host futures). -
http_hostdeclares the transport rows (three async, five sync readbacks);httpwraps them inHttpClient/RequestBuilder/Request/Response/ByteStream— async only at the points that really wait:use http::{ HttpClient, ClientQueryMethod }; let resp = await HttpClient.new().request() .method(ClientQueryMethod.Post) .url("https://example.com/api") .header("Accept", "application/json") .body(payload) .build() .send(cx);sendresolves at headers;body(cx)drains the wire;byte_stream()/next(cx)walk it in chunks. Status0is reserved for transport failure. The reqwest lane is native-only.
Mounting
mount_stdmountscore+calc;mount_std_asyncadds the async pair. Swappable packages mount by directory or bundle (module bundles); therutCLI mounts the tree packages a loose file names byuse(the rut CLI).inline = truepackages (ink, pouch, nmapset, json, strbuild, async_host) are source-inlined into each consumer — required for class-method and generic surfaces, which cannot cross a module link boundary (the frontend).- Generic functions cannot cross a module link boundary; exported linked surfaces carry only monomorphized fns.