Rust Components
Rust components are compiled to WebAssembly and loaded by Obelisk at startup. Each component defines
its interface in a WIT file and uses the wit-bindgen crate to generate Rust bindings.
The rust/ directory in the demo-tutorial repository
contains a complete Rust implementation of the same activity, workflow, and webhook as the JS
getting-started guide.
WIT world
A component's WIT lives under the wit/ directory:
wit/impl.wit— the world file: declares what the component exports (functions it provides) and imports (functions it calls).wit/deps/— WIT files for everything the world references: exported interfaces, imported interfaces from other components, generated extension interfaces, and Obelisk's built-in APIs.wit-bindgenresolves all package identities from this directory.
The world references files by package identity. Obelisk only cares about the exported interfaces; it ignores the world's package name and world name.
The demo-tutorial places the activity's own interface under
wit/tutorial_activity/and adds a symlinkwit/deps/tutorial_activity -> ../tutorial_activity. This is a project convention to keep your own interface files visually separate from deps — most projects put everything directly inwit/deps/.
Activity
An activity can export multiple interfaces, each containing multiple functions. It can also import Obelisk's logging API:
// wit/tutorial_activity/tutorial_activity.wit
package tutorial:activity;
interface activity-sleepy {
step: func(idx: u64, sleep-millis: u64) -> result<u64>;
}
// A second interface in the same package could be added here, or in another file.// wit/impl.wit
package any:any;
world any {
export tutorial:activity/activity-sleepy;
// optional: import obelisk:log/log@1.0.0; -- structured logging
}
See Runtime support for the full list of importable Obelisk interfaces, and WIT reference for the type and function syntax reference.
Workflow
A workflow exports its own interface and imports activities, other workflows, and Obelisk's workflow-support and logging APIs:
// wit/impl.wit
package any:any;
world any {
export tutorial:workflow/workflow;
import tutorial:activity/activity-sleepy; // activity to orchestrate
import tutorial:activity-obelisk-ext/activity-sleepy; // generated — enables join sets
import tutorial:workflow/other-workflow; // child workflow (also submittable)
import obelisk:workflow/workflow-support@5.1.0;
import obelisk:log/log@1.0.0;
}
All imported WIT files (the activity interface, the extension interface, other workflows, and
Obelisk's built-in APIs) must be present under wit/deps/.
The extension interface (activity-obelisk-ext) is generated by Obelisk from the activity's WIT. It
provides step_submit / step_await_next for submitting activities to a
join set without blocking. See
Extensions for details.
Webhook
A webhook imports the workflows (or activities) it calls. The HTTP handler export is implicit:
// wit/impl.wit
package any:any;
world any {
import tutorial:workflow/workflow;
}Bindgen and implementation
generate!({ generate_all }) reads the world file and generates Rust traits. Implement the Guest
trait on a unit struct:
Activity:
use exports::tutorial::activity::activity_sleepy::Guest;
use wit_bindgen::generate;
generate!({ generate_all });
struct Component;
export!(Component);
impl Guest for Component {
fn step(idx: u64, sleep_millis: u64) -> Result<u64, ()> {
std::thread::sleep(std::time::Duration::from_millis(sleep_millis));
Ok(idx)
}
}
Workflow (serial + parallel):
use exports::tutorial::workflow::workflow::Guest;
use obelisk::{log::log, types::time::{Duration, ScheduleAt}, workflow::workflow_support};
use tutorial::{
activity::activity_sleepy::step,
activity_obelisk_ext::activity_sleepy::{step_await_next, step_submit},
};
use wit_bindgen::generate;
generate!({ generate_all });
struct Component;
export!(Component);
impl Guest for Component {
fn serial() -> Result<u64, ()> {
let mut acc = 0;
for i in 0..10 {
workflow_support::sleep(ScheduleAt::In(Duration::Seconds(1)))?;
let result = step(i, i * 200)?; // calls the imported activity directly
acc += result;
log::info(&format!("step({i})={result}"));
}
Ok(acc)
}
fn parallel() -> Result<u64, ()> {
let mut handles = Vec::new();
for i in 0..10u64 {
let join_set = workflow_support::join_set_create();
step_submit(&join_set, i, i * 200); // non-blocking submit
handles.push((i, join_set));
}
let mut acc = 0;
for (i, join_set) in handles {
let (_id, result) = step_await_next(&join_set).unwrap();
acc = 10 * acc + result?;
workflow_support::sleep(ScheduleAt::In(Duration::Milliseconds(300)))?;
}
Ok(acc)
}
}
Webhook:
use tutorial::workflow::workflow;
use wit_bindgen::generate;
use wstd::http::{body::Body, Error, Request, Response, StatusCode};
generate!({ generate_all });
#[wstd::http_server]
async fn main(request: Request<Body>) -> Result<Response<Body>, Error> {
let path = request.uri().path_and_query().unwrap().as_str();
let response = match path {
"/serial" => {
let acc = workflow::serial().unwrap();
Response::builder().body(Body::from(format!("serial workflow completed: {acc}")))
}
_ => Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::from("not found")),
}?;
Ok(response)
}Cargo setup
All component crates must set crate-type = ["cdylib"]:
[lib]
crate-type = ["cdylib"]
[dependencies]
wit-bindgen = "..."
Activities and webhooks target wasm32-wasip2; workflows target wasm32-unknown-unknown.
Starting a new project
Use the official templates to scaffold a new project:
cargo generate obeli-sk/obelisk-templates