Skip to content

Rust Quick Start

Cargo.toml
[dependencies]
sayiir-runtime = "0.4" # core runtime + runners + macros
# Optional — pick your backend
sayiir-persistence = "0.4" # InMemoryBackend (dev/testing)
sayiir-postgres = "0.4" # PostgreSQL backend (production)

Import the prelude:

use sayiir_runtime::prelude::*;
// Re-exports: WorkflowBuilder, CheckpointingRunner, PooledWorker,
// WorkerHandle, InMemoryBackend, JsonCodec, TaskRegistry,
// task (macro), workflow (macro), etc.
use sayiir_runtime::prelude::*;
use sayiir_core::error::BoxError;
use std::sync::Arc;
#[task(id = "charge_card", timeout = "30s", retries = 3, backoff = "100ms")]
async fn charge(order: Order, #[inject] stripe: Arc<Stripe>) -> Result<Receipt, BoxError> {
stripe.charge(&order).await
}
// Generated: struct ChargeTask with new(stripe), task_id() → "charge_card",
// metadata(), register(), CoreTask impl.
// The original `charge` function is preserved for direct use/testing.

The #[task] macro generates a struct named {PascalCase}Task (e.g., fn chargeChargeTask) that implements CoreTask. The struct has new() (accepting injected deps), task_id(), metadata(), and register() methods. Your original function is kept intact so you can call it directly in tests.

OptionExampleDescription
idid = "charge_card"Recommended. Explicit task ID (default: fn name)
timeouttimeout = "30s"Task timeout (ms, s, m, h)
retriesretries = 3Max retry count
backoffbackoff = "100ms"Initial retry delay
backoff_multiplierbackoff_multiplier = 2.0Exponential multiplier (default: 2.0)
tagstags = "io"Categorization tags (repeatable)
#[task]
async fn validate(order: Order) -> Result<Order, BoxError> { Ok(order) }
#[task]
async fn charge(order: Order) -> Result<Receipt, BoxError> { Ok(Receipt { id: order.id }) }
#[task]
async fn send_email(receipt: Receipt) -> Result<(), BoxError> { Ok(()) }
let workflow = workflow! {
name: "order-process",
steps: [validate, charge, send_email]
}.unwrap();
SyntaxMeaning
task_nameReference to a #[task]-generated struct
name(param: Type) { expr }Inline task (must return Result)
a || bParallel fork (branches)
delay "5s"Durable delay
signal "name"Wait for external signal
,Sequential separator between steps

For scripts, tests, or quick experiments — run a workflow in one line with no backend or instance ID:

let status = workflow.run_once(input).await?;

This uses InProcessRunner internally. No durability, no persistence — just run and get the result. The run_once method is available on any Workflow via the WorkflowRunExt trait (included in the prelude).

For crash recovery and persistence, pair CheckpointingRunner with a durable backend:

use sayiir_postgres::PostgresBackend;
// Connects and runs migrations automatically
let backend = PostgresBackend::<JsonCodec>::connect("postgresql://localhost/sayiir").await?;
let runner = CheckpointingRunner::new(backend);
let status = runner.run(&workflow, "instance-001", input).await?;
// After a crash: pick up where it left off
let status = runner.resume(&workflow, "instance-001").await?;

For local development or tests that need checkpointing without a database:

let backend = InMemoryBackend::new();
let runner = CheckpointingRunner::new(backend);
let status = runner.run(&workflow, "instance-001", input).await?;