# Workflow basics - Rust SDK

> This section explains how to implement Workflows with the Rust SDK

## How to develop a Workflow 

Workflows are the fundamental unit of a Temporal Application and it all starts with the development of a [Workflow Definition](/workflow-definition).

In the Temporal Rust SDK programming model, a Workflow Definition is made of a Workflow struct and associated methods decorated with macros.

A Workflow is defined by:

1. A struct that holds the Workflow state
3. A `#[run]` method that contains the main Workflow logic
2. An optional `#[init]` method that initializes the Workflow
4. Optional `#[signal]`, `#[query]`, and `#[update]` methods for external interaction

```rust
use temporalio_macros::{workflow, workflow_methods};
use temporalio_sdk::{WorkflowResult, WorkflowContextView};

#[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());
        Ok(format!("Hello, {}!", name))
    }
}
```

The `#[workflow]` macro marks the struct as a Workflow. The `#[workflow_methods]` macro is applied to the `impl` block containing the Workflow methods.

### Workflow struct 

The Workflow struct holds the state of your Workflow Execution. This state is persisted and recovered during replays. All fields in a Workflow struct should be serializable.

### Workflow initialization 

The `#[init]` method is optional and is called when the Workflow first starts. It receives the initial Workflow input parameters and initializes the Workflow struct:

```rust
#[init]
fn new(_ctx: &WorkflowContextView, name: String, age: u32) -> Self {
    Self {
        name,
        age,
        started_at: Instant::now(),
    }
}
```

The `#[init]` method receives a `WorkflowContextView`, which provides read-only access to Workflow execution information.

### Run method 

The `#[run]` method is required and contains the main Workflow logic. It:

- Must be `async`
- Receives a mutable `WorkflowContext<Self>`
- Returns `WorkflowResult<T>` where T is the Workflow return type
- Executes exactly once per Workflow execution

```rust
#[run]
async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<String> {
    // Execute activities, timers, child workflows, etc.
    let result = ctx.start_activity(
        MyActivities::greet,
        name,
        ActivityOptions::start_to_close_timeout(Duration::from_secs(30))
    ).await?;

    Ok(result)
}
```

## Define Workflow parameters 

Temporal Workflows may have any number of custom parameters. However, we strongly recommend that structs are used as parameters, so that the object's individual fields may be altered without breaking the signature of the Workflow. All Workflow Definition parameters must be serializable.

A method annotated with `#[init]` can have any number of parameters. We recommend passing a single struct that contains all the input fields:

```rust
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
pub struct ProcessingInput {
    pub data: Vec<String>,
    pub timeout_seconds: u32,
}

#[workflow]
pub struct ProcessingWorkflow {
    data: Vec<String>,
    timeout_seconds: u32,
}

#[workflow_methods]
impl ProcessingWorkflow {
    #[init]
    fn new(_ctx: &WorkflowContextView, input: ProcessingInput) -> Self {
        Self {
            data: input.data,
            timeout_seconds: input.timeout_seconds,
        }
    }

    #[run]
    async fn run(_ctx: &mut WorkflowContext<Self>) -> WorkflowResult<String> {
        // Use the initialized state
        Ok("Processing complete".to_string())
    }
}
```

All Workflow input should be serializable by `serde`.

## Define Workflow return parameters 

Workflow return values must also be serializable. Returning results, returning errors, or throwing exceptions is fairly idiomatic in each language that is supported. However, Temporal APIs that must be used to get the result of a Workflow Execution will only ever receive one of either the result or the error.

The return type of a Workflow is `WorkflowResult<T>` where `T` implements `Serialize`. Success is represented by `Ok(value)` and failure by `Err(...)`:

```rust
#[run]
async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<ProcessingResult> {
    // Can return a complex result type
    let result = ProcessingResult {
        status: "completed".to_string(),
        records_processed: 100,
    };

    Ok(result)
}
```

## Customize your Workflow Type 

Workflows have a Type that is referred to as the Workflow name. By default, the Workflow type is the name of the Workflow struct. You can customize it by providing a `name` parameter to the `#[workflow]` macro:

```rust
#[workflow(name = "my-custom-workflow")]
pub struct GreetingWorkflow {
    name: String,
}
```

The Workflow Type defaults to the struct name if not specified. For example, this Workflow would have the type `GreetingWorkflow`:

```rust
#[workflow]
pub struct GreetingWorkflow {
    // ...
}
```

## Workflow logic requirements 

Workflow logic is constrained by [deterministic execution requirements](/workflow-definition#deterministic-constraints). For non-deterministic operations like API calls, and database queries, use [Activities](/develop/rust/activities/basics).

Workflow code must be deterministic because the Temporal Server may replay your Workflow to reconstruct its state. This means:

### Don't use nondeterministic functions

- No direct system time access - use `ctx.workflow_time()` instead of `SystemTime::now()`
- No random number generation - use `ctx.random_seed()` instead
- No external I/O (network, filesystem, etc.) - perform these in Activities instead
- No UUID generation via random means - the SDK doesn't have a direct UUID function, but you can use Activities for non-deterministic operations
- Do not use `tokio` or `futures` concurrency primitives directly in Workflow code. Many of them, like `tokio::select!`, `tokio::spawn`, `futures::select!`, introduce non-deterministic behavior that will break Workflow replay.

Instead, use the deterministic wrappers provided in `temporalio_sdk::workflows`:
    - `select!` — deterministic select (polls in declaration order)
    - `join!` — deterministic join for a fixed number of futures
    - `join_all` — deterministic join for a dynamic collection of futures

### Use Workflow-safe primitives

The Rust SDK provides:

- `ctx.timer()` - Wait for a duration
- `ctx.wait_condition(closure)` - Wait until a condition is true
- `workflows::select!` - Deterministic select statement
- `ctx.start_activity()` - Execute Activities
- `ctx.start_local_activity()` - Execute local Activities
- `ctx.child_workflow()` - Execute child Workflows
- `ctx.cancelled()` - Check if Workflow is cancelled

```rust
use std::time::Duration;

#[run]
async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<String> {
    // Good - deterministic timer
    ctx.timer(TimerOptions {
        duration: Duration::from_secs(60),
        summary: Some("important timer".into())
    }).await;

    // Good - deterministic wait for condition
    ctx.wait_condition(|s| s.data.len() >= 3).await;

    // Bad - nondeterministic sleep
    // tokio::time::sleep(Duration::from_secs(10)).await;

    // Bad - nondeterministic time
    // SystemTime::now()

    Ok("Done".to_string())
}
```

## Access Workflow State 

Use `ctx.state()` for read-only access and `ctx.state_mut()` for mutable access to your Workflow state:

```rust
#[run]
async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<String> {
    // Read-only access
    let name = ctx.state(|s| s.name.clone());

    // Mutable access (for signal handlers or update handlers)
    // Available in sync methods

    Ok(name)
}
```

In synchronous [`Signal`](/develop/rust/workflows/message-passing#signals) and [`Update`](/develop/rust/workflows/message-passing#updates) handlers, you can mutate state directly via `&mut self`.

## Workflow return types 

The `#[run]` method must return `WorkflowResult<T>`. This is a type alias for `Result<T, WorkflowTermination>`. 

For errors, use `WorkflowTermination::Failed` which can be constructed from other error types via `into()`:

```rust
#[run]
async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<String> {
    if some_validation_fails {
        return Err(anyhow::anyhow!("validation_failed: Input is invalid").into());
    }

    Ok("Success".to_string())
}
```

Workflow errors will cause the Workflow Execution to fail and the error details will be available to clients.
