Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The standard library

The standard library splits in two. core is the only true standard — a prelude of builtin names that are ambient: in scope in every compilation unit, no use needed. Everything else is a set of swappable packages shipped in the toolchain tree — a program that wants one says so (a use line; the CLI mounts the tree packages for loose files automatically, and a package program lists them in its manifest). Any of them can be replaced wholesale; the engine knows none of their names.

The reference page is core and the swappable packages.

What’s always there (core)

Reporting and bugs

panic("Rect: negative extents");       // abort with a message
assert(total == expected, "checksum"); // abort when false (message optional)

Integer safety ladder

Every integer width carries compiler-lowered methods. Plain +/-/* trap on overflow; these never do:

use ink::{ Logger };

pub fn main() {
    let log = Logger.new("ladder");
    let mut v: u8 = 250;
    v = v.wrapping_add(10);            // 4 — two's-complement wrap
    let over = 200u8.checked_add(100); // (44, false) — .1 false = escaped
    let sat = 200u8.saturating_add(100); // 255 — clamp at the bounds
    log.info(f"v={v} over=({over.0}, {over.1}) sat={sat}");
}
v=4 over=(44, false) sat=255

checked_* answers (T, bool); .0 holds the wrapped bits either way. Pick per call site: a checksum wraps, a length checks.

str members

use ink::{ Logger };

pub fn main() {
    let log = Logger.new("str");
    let s = "héllo rut";
    log.info(f"len={s.len()} first={s.code()}");
    log.info(f"at1={s.code_at(1)} octets={s.encode().len()}");
    log.info(f"view={s.slice(6, 9)}");
    let head: str = "héllo";
    log.info(f"starts={s.starts_with(0, head)}");
    let joined = string_join(["rut", "runs"]);
    log.info(f"joined={joined}");
    let h = str.from_code(72);
    log.info(f"from_code={h}");
}
len=9 first=104
at1=233 octets=10
view=rut
starts=true
joined=rutruns
from_code=H

s.slice deserves a second look: no octets move; the view records a window over the parent, prints, compares by content, iterates, and can re-slice. Codepoint access is spelled with integers — str.from_code(n) builds the 1-codepoint str for a u32. There is no split primitive; tokenizing rides s.scan(from, set) over a caller-owned [u8] class table. See string slicing and views.

bytes members

use ink::{ Logger };

pub fn main() {
    let log = Logger.new("bytes");
    let b = "rut runs".encode();
    log.info(f"len={b.len()} decode={b.decode()}");
    let copy = b.clone();     // the ONLY copy escape hatch
    let z = bytes.zeroed(4);
    let from = bytes.from([1, 2, 3]);
    log.info(f"same={copy == b} zeroed={z.len()} from={from.len()}");
}
len=8 decode=rut runs
same=true zeroed=4 from=3

Erasure and cleanup

opaque(v) seals any value for recovery with opaque.downcast<T>(o) -> ?T — see errors and optionality. on_drop(p, cleanup) runs a callback when a cell’s refcount reaches zero, and Weak.new(v) holds a non-keeping reference (upgrade() -> ?T, nil once the referent died) — the memory stories live in the Rc heap and destructors and weak references.

StrBuf — the raw growable builder

Prefer the package face below; the engine cell under it is StrBuf(cap) with push/push_code/len/finish.

One gated name

NAN is core’s single constant, deliberately name-explicit: use core::{ NAN };. The float constants live in calc.

pouch — the growable sequence

use pouch::{ Vec };
use ink::{ Logger };

pub fn main() {
    let log = Logger.new("pouch");
    let mut xs: Vec<i32> = Vec.new();         // or Vec.with_capacity(64)
    xs.push(10);                              // amortized O(1)
    let v = Vec<f32>.filled(0.0, 1024);       // n slots of one value
    let w = Vec<i32>.from([1, 2, 3]);         // from a fixed array (copies)
    let x = xs[0];                            // indexing
    xs[0] = 42;                               // needs a `mut` binding
    let last = xs.pop();                      // removes + returns; traps if empty
    let n = xs.len();
    let mut total = 0;
    for (let e of w) {                        // iteration
        total += e;
    }
    log.info(f"x={x} last={last} len-after-pop={n} filled={v.len()} total={total}");
}
x=10 last=42 len-after-pop=0 filled=1024 total=6

Vec<u8> is the mutable byte builder: push bytes, then freeze() into the immutable bytes. xs.as_array() copies the live elements into a fixed [T]. A slice(from, to) window is compiler-lowered — an O(1) view that writes through to the parent vector.

nmapset — keyed collections

use nmapset::{ HashMap, HashSet };
use ink::{ Logger };

pub fn main() {
    let log = Logger.new("nmapset");
    let mut counts: HashMap<str, i32> = HashMap.new();
    let fresh = counts.put("rut", 1);          // answers true when the key was NEWLY added
    let mut n = 0;
    let hit = counts.get("rut");   // ?i32 — nil means absent
    if (hit != nil) { n = hit + 1; }
    let there = counts.has("rut");             // membership
    let gone = counts.remove("runs");         // answers whether it was there
    let size = counts.len();

    let mut seen: HashSet<str> = HashSet.new();
    let first = seen.put("x");     // true — newly added
    let again = seen.put("x");     // false
    log.info(f"fresh={fresh} n={n} has={there} removed={gone} len={size}");
    log.info(f"first={first} again={again}");
}
fresh=true n=2 has=true removed=false len=1
first=true again=false

Keys come from a fixed set — integers, bool, str, bytes (no floats: they have no stable equality contract) — hashed by the host; a user-defined key escapes by encoding canonically to bytes. A hit returns the stored cell, not a copy. There is no iteration surface: maps and sets answer questions, they don’t walk.

strbuild — the string builder

use strbuild::{ StringBuilder };
use ink::{ Logger };

pub fn main() {
    let log = Logger.new("strbuild");
    let n = 3;
    let mut b = StringBuilder.new();     // or StringBuilder.with_cap(1024)
    b.append("count: ");
    b.append_code(33);                   // one codepoint
    b.append(f" up to {n}");             // appends are amortized O(1)
    let s = b.build();                   // the ONE materialization; builder keeps its buffer
    log.info(s);
}
count: ! up to 3

Every out = f"{out}{chunk}" loop copies the whole prefix each time; the builder appends into one growable cell and copies once, at build().

calc — float math

use calc::{ Math };
use ink::{ Logger };

pub fn main() {
    let log = Logger.new("calc");
    let x: f64 = -2.0;
    let a: f64 = 3.0;
    let b: f64 = 7.0;
    let d = Math.sqrt(2.0);        // f64 host fns: sin, pow, atan2, fma, ...
    let f = Math.sqrt_f(2.0f32);   // f32 twins under a `_f` suffix
    let mx = Math.max(a, b);
    let sg = Math.signum(x);
    log.info(f"sqrt={d} sqrt_f={f} pi={Math.PI}");
    log.info(f"abs={Math.abs(x)} min={Math.min(a, b)} max={mx} signum={sg}");
}
sqrt=1.4142135623730951 sqrt_f=1.4142135 pi=3.141592653589793
abs=2 min=3 max=7 signum=-1

rut has no overloading, so the width lives in the name. The integer ladder is not here — those are core’s, always available.

json — encode and decode

use json::{ decodeJson, encodeJson };
use ink::{ Logger };

pub fn main() {
    let log = Logger.new("json");
    let (n, e) = decodeJson<i64>("42");            // (?T, ?E) — see errors
    let (s, ee) = encodeJson<[i64]>([1, 2, 3]);    // (?str, ?EncodeJsonError)
    if (e == nil && ee == nil) {
        let text = s;
        log.info(f"n={n} s={text}");
    }
}
n=42 s=[1,2,3]

Decode is direct and schema-driven: your type’s impl JsonDeserialize reads exactly the fields it expects, no intermediate tree. Your types opt in with two small impls; the container impls (Vec<T>, the map/set family) mount automatically when those packages are in your program. Error values are a kind enum plus a struct with .at/.got/.expected — see errors and optionality.

ink — logging

There is no console, no print — all output goes through a logger:

use ink::{ Logger };

pub fn main() {
    let n = 3;
    let log = Logger.new("app");
    log.info(f"started with {n} items");
    log.debug("..."); log.warn("..."); log.error("...");
}
started with 3 items
...
...
...

Logger is a plain rut class over a host-provided handle; the embedder chooses the sink (the rut CLI prints to stdout). The same pattern — declare a host surface in a .d.rut, wrap it in a rut class — is how any embedder package reaches rut code; see embedding and native modules.

http

The async HTTP client lives with the concurrency chapter — builder construction, send(cx) resolving at headers, body drains and byte streams — in async: tasks, workers, and channels.

Put it together

use pouch::{ Vec };
use strbuild::{ StringBuilder };
use calc::{ Math };
use ink::{ Logger };

pub fn main() {
    let log = Logger.new("std");

    // Vec: build, pop, read
    let mut v: Vec<i32> = Vec.new();
    v.push(10);
    v.push(20);
    v.push(30);
    let last = v.pop();
    log.info(f"len={v.len()} last={last} first={v[0]}");

    // fixed arrays + string_join from core
    let parts: Vec<str> = Vec.from(["rut", "runs"]);
    log.info(f"joined={string_join(parts.as_array())}");

    // StringBuilder: amortized appends, one materialization
    let mut b = StringBuilder.new();
    b.append("count: ");
    b.append_code(33);
    b.append(f" up to {v.len()}");
    let s = b.build();
    log.info(s);

    // calc's Math namespace (f64) and its f32 twins (_f)
    let d = Math.sqrt(2.0);
    let f = Math.sqrt_f(2.0);
    log.info(f"sqrt2 f64~{d} f32~{f} pi={Math.PI}");

    // assert: the builtin bug-catcher
    assert(v.len() == 2, "vec should hold two");
    log.info("asserted");
}
len=2 last=30 first=10
joined=rutruns
count: ! up to 2
sqrt2 f64~1.4142135623730951 f32~1.4142135 pi=3.141592653589793
asserted

That completes the tutorial. From here, the core concepts explain why the language is shaped the way it is, and the reference pins down every rule.