Skip to main content

Continue-As-New - Rust SDK

This page answers the following questions for Rust developers:

What is Continue-As-New?

Continue-As-New lets a Workflow Execution close successfully and creates a new Workflow Execution. You can think of it as a checkpoint when your Workflow gets too long or approaches certain scaling limits.

The new Workflow Execution is in the same chain; it keeps the same Workflow Id but gets a new Run Id and a fresh Event History. It also receives your Workflow's usual parameters.

How to Continue-As-New using the Rust SDK

First, design your Workflow parameters so that you can pass in the "current state" when you Continue-As-New into the next Workflow run. This state is typically passed as a parameter or stored in the Workflow struct.

Inside your Workflow, return a WorkflowTermination::ContinueAsNew error to continue as new:

use temporalio_sdk::workflow::WorkflowTermination;

#[workflow]
pub struct IterativeWorkflow {
current_index: u32,
total_items: u32,
batch_size: u32,
}

#[workflow_methods]
impl IterativeWorkflow {
#[init]
fn new(
_ctx: &WorkflowContextView,
total_items: u32,
batch_size: u32,
current_index: u32,
) -> Self {
Self {
current_index,
total_items,
batch_size,
}
}

#[run]
async fn run(ctx: &mut workflow::WorkflowContext<Self>) -> WorkflowResult<String> {
let (current_index, total_items, batch_size) = ctx.state(|s| {
(s.current_index, s.total_items, s.batch_size)
});

// Process a batch of items
for i in current_index..(current_index + batch_size).min(total_items) {
ctx.activity(
MyActivities::process_item,
i,
ActivityOptions {
start_to_close_timeout: Some(Duration::from_secs(60)),
..Default::default()
},
).await?;
}

let next_index = current_index + batch_size;

if next_index >= total_items {
// All items processed
Ok(format!("Processed all {} items", total_items))
} else {
// Continue with the next batch
let new_state = IterativeWorkflow::new(
&WorkflowContextView::default(),
total_items,
batch_size,
next_index,
);

Err(WorkflowTermination::continue_as_new(new_state))
}
}
}

The WorkflowTermination::continue_as_new() method accepts the input to pass to the next Workflow run.

Considerations for Workflows with Message Handlers

If you use Signals or Updates, don't call Continue-As-New from within those handlers. Instead, wait for your handlers to finish in the main Workflow run before you call continue_as_new.

#[workflow]
pub struct WorkflowWithSignals {
count: i32,
should_continue: bool,
}

#[workflow_methods]
impl WorkflowWithSignals {
#[run]
async fn run(ctx: &mut workflow::WorkflowContext<Self>) -> WorkflowResult<String> {
// Wait for signal to be processed
ctx.wait_condition(|s| s.should_continue).await;

// Process work
for _ in 0..100 {
ctx.activity(
MyActivities::do_work,
(),
ActivityOptions {
start_to_close_timeout: Some(Duration::from_secs(60)),
..Default::default()
},
).await?;
}

// Now safe to continue as new
Err(WorkflowTermination::continue_as_new(
WorkflowWithSignals {
count: self.count + 100,
should_continue: false,
}
))
}

#[signal]
fn signal_continue(&mut self) {
self.should_continue = true;
}
}

When is it right to Continue-as-New using the Rust SDK?

Use Continue-As-New when:

  1. Your Workflow has a very long Event History (millions of events)
  2. Your Workflow processes items in batches and needs to reset state
  3. You're implementing a scheduler that needs to run indefinitely
  4. Your Workflow's Event History is approaching limits

The Temporal Service tracks your Workflow's progress against these limits. Generally, when your Workflow's Event History gets too large (millions of events), continuing as new prevents performance degradation and ensures your Workflow can continue indefinitely.

Example: Long-Running Scheduler

Here's a practical example of a Workflow that continues as new to implement a long-running scheduler:

#[workflow]
pub struct SchedulerWorkflow {
iteration: u32,
}

#[workflow_methods]
impl SchedulerWorkflow {
#[init]
fn new(_ctx: &WorkflowContextView, iteration: u32) -> Self {
Self { iteration }
}

#[run]
async fn run(ctx: &mut workflow::WorkflowContext<Self>) -> WorkflowResult<String> {
let iteration = ctx.state(|s| s.iteration);

// Process scheduled work for this iteration
ctx.activity(
SchedulerActivities::run_scheduled_tasks,
iteration,
ActivityOptions {
start_to_close_timeout: Some(Duration::from_secs(300)),
..Default::default()
},
).await?;

// Wait for the next iteration (24 hours)
ctx.timer(Duration::from_secs(86400)).await;

// Continue as new for the next iteration
Err(WorkflowTermination::continue_as_new(
SchedulerWorkflow {
iteration: iteration + 1,
}
))
}
}

This Workflow can run indefinitely, keeping a fresh Event History for each day's operations.

Best Practices

  1. Pass all necessary state: When continuing as new, include all state the next run needs
  2. Use meaningful iteration markers: Include iteration numbers or timestamps to track progress
  3. Test your state passing: Ensure parameters serialize and deserialize correctly
  4. Don't continue as new too frequently: It's better to have some Event History than to continue as new on every execution
  5. Consider batch sizes: Find the right balance between batch size and number of continues as new