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:

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 symlink wit/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 in wit/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@6.0.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)), None)?;
            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 {
            // `-await-next` returns the child result directly; read the id from
            // `join_set.last_id()` if you need it.
            let result = step_await_next(&join_set).unwrap();
            acc = 10 * acc + result?;
            workflow_support::sleep(ScheduleAt::In(Duration::Milliseconds(300)), None)?;
        }
        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