The Temporal Rust SDK is now generally available. Rust developers can now write Workflows and Activities, run Workers, and interact with Temporal through native Rust APIs.
When an application coordinates APIs, databases, queues, or services, it must keep track of what completed, what failed, and what should happen next. Teams often build this logic with retry loops, state tables, queue consumers, timeout handlers, and recovery jobs.
Temporal handles that execution state. Developers write the application flow as a Workflow and put calls to external systems in Activities. Temporal records the Workflow’s progress in Event History. If a Worker stops, an available Worker can replay that history, rebuild the Workflow state, and continue the execution.
The Rust SDK brings that programming model to familiar Rust constructs. Workflow state lives in Rust structs. Workflows and Activities use async functions. Procedural macros define Workflows and Activities, while typed method references connect calls to their inputs and results. Workers and Activities integrate with Tokio.
From an Activity to a result#
The following example comes directly from the Rust SDK README.
First, define the Activities:
use temporalio_macros::activities;
use temporalio_sdk::activities::{ActivityContext, ActivityError};
use std::sync::{Arc, atomic::{AtomicUsize, Ordering}};
struct MyActivities {
counter: AtomicUsize,
}
#[activities]
impl MyActivities {
#[activity]
pub async fn greet(_ctx: ActivityContext, name: String) -> Result<String, ActivityError> {
Ok(format!("Hello, {}!", name))
}
// Activities can also use shared state via Arc<Self>
#[activity]
pub async fn increment(self: Arc<Self>, _ctx: ActivityContext) -> Result<u32, ActivityError> {
Ok(self.counter.fetch_add(1, Ordering::Relaxed) as u32)
}
}
Then define the Workflow:
use temporalio_macros::{workflow, workflow_methods};
use temporalio_sdk::{WorkflowContext, WorkflowContextView, WorkflowResult};
use std::time::Duration;
#[workflow]
pub struct GreetingWorkflow {
name: String,
}
#[workflow_methods]
impl GreetingWorkflow {
#[init]
fn new(_ctx: &WorkflowContextView, name: String) -> Self {
Self { name }
}
#[run]
async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<String> {
let name = ctx.state(|s| s.name.clone());
// Execute an activity
let greeting = ctx.execute_activity(
MyActivities::greet,
name,
ActivityOptions::start_to_close_timeout(Duration::from_secs(10))
)?.await?;
Ok(greeting)
}
}
The macros turn Rust methods and structs into Temporal definitions. The Workflow calls the Activity through the typed MyActivities::greet method reference, so Rust can check the call’s input and result types. In a production application, the Activity could call an API, write to a database, or process a file.
Next, register both definitions with a Worker:
use temporalio_client::{Client, ClientOptions, Connection, envconfig::LoadClientConfigProfileOptions};
use temporalio_sdk::{Runtime, Worker, WorkerOptions};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let runtime = Runtime::from_current_tokio(Default::default())?;
let (conn_options, client_options) = ClientOptions::load_from_config(
LoadClientConfigProfileOptions::default()
)?;
let connection = Connection::connect(conn_options).await?;
let client = Client::new(connection, client_options)?;
let worker_options = WorkerOptions::new("my-task-queue")
.register_activities(MyActivities { counter: Default::default() })
.register_workflow::<GreetingWorkflow>()?
.build();
Worker::new(&runtime, client, worker_options)?.run().await?;
Ok(())
}
The Worker runs on Tokio, polls the Task Queue, and executes registered Workflow and Activity code. The SDK can load connection settings from environment variables or a temporal.toml file.
A separate client can then start the Workflow and wait for its result:
use temporalio_client::{
Client, ClientOptions, Connection,
envconfig::LoadClientConfigProfileOptions,
WorkflowOptions, GetWorkflowResultOptions,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (conn_options, client_options) = ClientOptions::load_from_config(
LoadClientConfigProfileOptions::default()
)?;
let connection = Connection::connect(conn_options).await?;
let client = Client::new(connection, client_options);
// Start a workflow
let handle = client.start_workflow(
GreetingWorkflow::run,
"World".to_string(),
WorkflowOptions::new("my-task-queue", "greeting-workflow-1").build()
).await?;
// Wait for the result
let result = handle.get_result(GetWorkflowResultOptions::default()).await?;
Ok(())
}
The returned handle can wait for the result or interact with the running Workflow. The Workflow Execution itself is not tied to the client process; the client can disconnect without stopping it.
Activity I/O and Workflow determinism#
Workers run on Tokio, and Activity implementations can use normal async I/O. Activities can call services, access files, use network clients, and run side-effecting code.
Because an Activity may run more than once, Activity code that changes external state should be idempotent. Long-running Activities can heartbeat to report progress and receive cancellation.
Workflows have a different job. Temporal may replay a Workflow from its Event History to rebuild its state. Given the same history, the Workflow must issue the same commands in the same order. Direct I/O, system time, threads, global mutable state, and nondeterministic scheduling can break that requirement.
For that reason, Workflow code uses Temporal’s deterministic alternatives. A durable timer is the simplest example:
ctx.timer(Duration::from_secs(60)).await;
Unlike tokio::time::sleep, the timer is represented in Event History. When it fires, Temporal schedules another Workflow Task, and the Workflow can continue on an available Worker. ctx.workflow_time() provides Workflow time instead of wall-clock time. For concurrency, the SDK provides deterministic select!, join!, and join_all implementations instead of relying directly on tokio or futures primitives whose polling order may change.
The SDK also includes a runtime nondeterminism detector, enabled by default. It tracks whether asynchronous wake-ups inside Workflow code come from SDK operations or from outside sources. When it observes a wake from outside an SDK primitive. For example, from a Tokio timer, async I/O, a spawned task, or an async channel, it fails the Workflow Task.
The detector cannot catch every change in polling order or synchronous calls such as SystemTime::now() and rand::random(). Keep Workflow code deterministic. Put API calls, database access, file I/O, and other side effects in Activities.
Test code and replay history#
With the testing feature, Activity inputs and outputs are ordinary Rust values that tests can pass directly. Workflow tests can start an isolated Temporal CLI development server and use the same client and Worker APIs as the application.
Before deploying a Workflow change, WorkflowReplayer can run the new code against recorded histories. This checks whether the new code still produces commands compatible with executions that have already started.
What developers can build#
A Workflow can run for seconds, days, or longer. An application can send Signals to a running Workflow, use Queries to read its state, and use Updates to change its state and receive a result.
The SDK also supports timers, retries, timeouts, cancellation, Child Workflows, Continue-As-New, patching, interceptors, and tracing. Developers can use these APIs for payment flows, account provisioning, order processing, data pipelines, infrastructure automation, approval processes, and AI agents.
Available now#
The Temporal Rust SDK is generally available and works with Temporal Cloud and self-hosted Temporal. The quickstart shows how to add the published Rust crates, start a local development server, run a Worker, and start your first Workflow Execution.
Start here:
Try it and share your feedback in #rust-sdk in the Temporal Community Slack.