These calls are in substrio_wasm::platform and have the same behavior and errors as their JavaScript
counterparts. An error is an Error whose code is the platform error code, matching the code thrown by
the JavaScript call. Read its message with substrio_wasm::db::last_error_message().
Waiting
A Rust call runs on its thread from start to finish. Where JavaScript awaits a promise, Rust waits on a handle.
Fetch::wait() waits for one response, sleep(ms) waits for a duration, and run_until_idle() waits until
every timer has fired and every fetch has ended. Timers that become due still fire while the call waits.
Timers belong to the function. Setting or clearing one makes no platform call.
Waiting does not consume CPU time or count against the CPU limit. The call's
wall-time limit still applies and ends the wait with 504 CF_WALL_TIMEOUT, as
it does for a JavaScript call.
A fetch still in progress when the call returns is ended, and a pending object reply is dropped. Timers that have not fired are also dropped. JavaScript clears a call's timers the same way.
The calls
| Rust | JavaScript | What it does |
|---|---|---|
objects(class) |
substrio.objects[class] |
The namespace of a class your app exports. |
ns.id_from_name(name) |
ns.idFromName(name) |
The stable id for a name in that class. Both languages return the same id. |
ns.new_unique_id() |
ns.newUniqueId() |
A new random id. |
ns.id_from_string(hex) |
ns.idFromString(hex) |
The id represented by 32 hex digits. |
ns.get(&id) |
ns.get(id) |
The object's stub. |
stub.fetch(&request) |
stub.fetch(request) |
A fetch turn of the object. Its reply is read as a response. |
ns.storage(&id) |
state.storage |
The storage of an object, during a turn of that object. |
storage.get(key) |
storage.get(key) |
The value's JSON text, or None. |
storage.put(key, json) |
storage.put(key, value) |
Keeps the value, written with the turn's reply. |
storage.delete(key) |
storage.delete(key) |
true when the key held a value. |
storage.delete_all() |
storage.deleteAll() |
Removes every value of the object. |
storage.list(&options) |
storage.list(options) |
The keys and values in key order. |
storage.get_alarm() |
storage.getAlarm() |
The alarm's time in Unix milliseconds, or None. |
storage.set_alarm(ms) |
storage.setAlarm(ms) |
Sets the alarm. |
storage.delete_alarm() |
storage.deleteAlarm() |
Removes the alarm. |
fetch(&request) |
fetch(url, init) |
Sends a request and gives a handle to wait on. |
fetch.wait() |
await fetch(...) |
The response, or an error. |
wait_all(fetches) |
Promise.all(fetches) |
Every response, in order. |
set_timeout(ms, f) |
setTimeout(f, ms) |
Runs f once after ms milliseconds. |
set_interval(ms, f) |
setInterval(f, ms) |
Runs f every ms milliseconds, with a minimum interval of 1 millisecond. |
clear_timeout(id) |
clearTimeout(id) |
The timer does not fire. |
clear_interval(id) |
clearInterval(id) |
The interval stops. |
sleep(ms) |
await new Promise((r) => setTimeout(r, ms)) |
Waits, and the call's timers fire meanwhile. |
run_until_idle() |
awaiting all of the handler's work | Waits until nothing is left to run. |
get_random_values(&mut bytes) |
crypto.getRandomValues(bytes) |
Fills the bytes from the platform random source. |
random_uuid() |
crypto.randomUUID() |
A version 4 UUID. |
performance_now() |
performance.now() |
Milliseconds on the same clock, from the same origin. |
date_now() |
Date.now() |
Unix milliseconds. A test tenant reads the clock the tenant keeps. |
A storage value is JSON text, the same form used by JavaScript storage. Either language can read values written
by the other. A string is stored with its quotes, so put("name", "\"Ada\"") reads in JavaScript as "Ada".
A request is built with FetchRequest::get(url), FetchRequest::post(url) or FetchRequest::new(method, url),
then .header(name, value), .text(body) or .bytes(body). A text body without a Content-Type header is sent as
text/plain;charset=UTF-8, as JavaScript sends a string body. A bytes body gets no type. The method is sent in
capitals, as JavaScript sends every method, and a request without one is a GET.
A response provides status(), ok(), status_text(), url(), headers(), header(name), body(), and text().
status_text() is the reason phrase the server sent on its status line, such as "Created" for a 201, and JS's
statusText is the same text. A server that sends no phrase gives an empty string.
Header names are lower case, and a repeated name's values are joined with ", ".
A fetch uses the same platform implementation as JavaScript, including the private-address restrictions,
allowances, and limits (cf_fetch_limit, cf_fetch_bytes_mb, and the response size limit). A fenced sandbox
keeps the request in its outbox, and a test tenant's stubs respond as they do for JavaScript.
Object classes in Rust
A Rust function can export durable object classes, as a JavaScript module exports a class. Each class has a
fetch handler, an alarm handler, or both. They are exported once, with export_object!, beside export!.
use substrio_wasm::platform::{self as p, AlarmInfo, ObjectState};
fn room_fetch(state: &ObjectState, req: &Request, _env: &Env) -> Response {
let storage = state.storage().unwrap();
let n = storage.get("n").unwrap().and_then(|v| v.parse::<u64>().ok()).unwrap_or(0) + 1;
storage.put("n", &n.to_string()).unwrap();
Response::text(200, &format!("{} {}", req.method(), n))
}
fn room_alarm(state: &ObjectState, _info: &AlarmInfo, _env: &Env) -> Result<(), db::Error> {
state.storage()?.delete_all()
}
substrio_wasm::export_object!(Room { fetch: room_fetch, alarm: room_alarm });
The class name is its identifier, as with a JavaScript class. Any function in the app can access it through
objects("Room") in Rust or substrio.objects.Room in JavaScript. Both address the same object for the same id.
A Rust class turn runs like a JavaScript turn. The object has one home thread, its turns run one at a time, and
storage writes are applied before its reply. state.storage() gives the object's storage, state.id() its id,
and state.class_name() its class. fetch receives the stub's request and returns a response. alarm receives
AlarmInfo (retry_count and is_retry), and an Err is retried like a thrown JavaScript alarm. A class
without fetch or alarm refuses the corresponding turn with the same message as JavaScript.
A turn that calls the stub of the object it runs for is refused with INVALID_ARGUMENT, because the call would
wait for the turn that made it. JavaScript refuses it too.
A function whose code fetches, sleeps or calls an object is always run on the server's function threads. A thread that serves connections never runs it, so a slow upstream never holds other requests. An object's home thread is one of the function threads too, so a turn that waits never holds a connection thread.
The classes are read from the compiled module when the function is deployed. A module that names a class without
exporting its turns, or exports turns without naming a class, is refused, and so is a class name that is not an
identifier (letters, digits, _ and $, not starting with a digit). export_object! writes both parts, so a
function that uses it is never refused for this.
Refusals
| Code | When |
|---|---|
INVALID_ARGUMENT |
The URL does not start with http:// or https://, a header is refused, the class name is not a name, the id is not 32 hex digits, the id belongs to another class, or a turn calls the stub of the object it runs for. |
VALUE_TOO_LARGE |
A request body or a response passes the size limit, or get_random_values is asked for more than 65536 bytes. |
FUNCTION_LIMIT |
The call has made cf_fetch_limit fetches already. |
PERMISSION_DENIED |
The private-address fence refused the connection. |
FETCH_FAILED |
The transfer failed, or no test stub answers a test tenant's fetch. |
CF_TENANT_MEMORY |
The response would pass the tenant's function memory budget. |
BANDWIDTH_QUOTA_EXCEEDED |
The call's responses passed cf_fetch_bytes_mb. |
STORAGE_IO |
A fenced sandbox's outbox could not keep the request. |
UNSUPPORTED_OPERATION |
This server serves no durable objects. |
NO_CALL_CONTEXT |
An object's storage is used outside a turn of that object. |
CF_EXECUTION_FAILED |
The object's turn failed. The error code of the object is passed on when it has one. |
CF_WALL_TIMEOUT |
The call's wall limit passed while it waited. The call ends with 504. |
Example
#![no_std]
extern crate alloc;
use alloc::format;
use substrio_wasm::platform::{self as p, FetchRequest};
use substrio_wasm::{Ctx, Env, Request, Response};
fn fetch(_req: &Request, _env: &Env, _ctx: &Ctx) -> Response {
let rooms = match p::objects("Room") {
Ok(ns) => ns,
Err(e) => return Response::json(500, format!("{{\"code\":{}}}", e.code)),
};
let id = rooms.id_from_name("lobby").unwrap();
let pending = rooms.get(&id).unwrap().fetch(&FetchRequest::post("https://room/join").text("ada")).unwrap();
let started = p::performance_now();
match pending.wait() {
Ok(r) => Response::json(200, format!("{{\"status\":{},\"ms\":{}}}", r.status(), p::performance_now() - started)),
Err(e) => Response::json(500, format!("{{\"code\":{}}}", e.code)),
}
}
substrio_wasm::export!(fetch);
The Room class is exported by a JavaScript function of the same app. The Rust function calls its fetch and
reads the reply as it reads any response.
See also Rust functions, the entry page: when to pick Rust, every call exposed to it, deploy and testing.