Skip to content

Zart is in active development — breaking API changes may occur despite our best efforts to keep contracts stable.

Capture Variables

Capture variables let you durably record a value in a single expression, without defining a full step. They’re perfect for timestamps, environment reads, generated identifiers, or any pure computation that needs to survive process restarts.

Sometimes you need to capture a value durably, but defining a full step just to return Utc::now() feels excessive:

// Before captures — a full step definition just to record a timestamp
#[zart_step("capture-started-at")]
async fn capture_started_at() -> Result<String, StepError> {
Ok(chrono::Utc::now().to_rfc3339())
}
// In the durable handler:
let started_at: String = capture_started_at().await?;

With zart::capture, this collapses to a single line:

let started_at = zart::capture("started-at", || chrono::Utc::now()).await?;

A capture evaluates a closure once, persists the result, and returns the value. On replay after a restart, the cached value is returned — the closure is never re-evaluated.

// First run: evaluates the closure, writes to database, returns the value
// Replay: returns the cached value from the database
let started_at = zart::capture("started-at", || chrono::Utc::now()).await?;

For the common case of capturing the current UTC timestamp, use the built-in now() function:

let started_at = zart::now("started-at").await?;
zart::sleep("initial-wait", Duration::from_secs(5)).await?;
let resumed_at = zart::now("resumed-at").await?;

This is shorthand for zart::capture("started-at", chrono::Utc::now).await?.

use zart::prelude::*;
use zart::zart_durable;
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SleepInput {
task_name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SleepOutput {
task_name: String,
started_at: String,
resumed_at: String,
}
struct SleepTask;
#[async_trait::async_trait]
impl DurableExecution for SleepTask {
type Data = SleepInput;
type Output = SleepOutput;
async fn run(&self, data: Self::Data) -> Result<Self::Output, TaskError> {
let started_at = zart::now("started-at").await?;
zart::sleep("initial-wait", Duration::from_secs(5)).await?;
let resumed_at = zart::now("resumed-at").await?;
Ok(SleepOutput {
task_name: data.task_name,
started_at: started_at.to_rfc3339(),
resumed_at: resumed_at.to_rfc3339(),
})
}
}
ScenarioUse Capture?
Recording a timestamp✅ Yes
Reading an environment variable✅ Yes
Generating a UUID or correlation ID✅ Yes
Pure computation (hash, encode, transform)✅ Yes
HTTP call or database write❌ No — use #[zart_step]
Anything with side effects❌ No — use #[zart_step]
Work that needs retries❌ No — use #[zart_step] with retry config

Captures must be pure — no side effects, no I/O. The closure may never run again after the first evaluation.

Just like regular steps, each capture must have a unique, stable name within an execution. The name is the database key used to find the cached value on replay.

// ✅ Good — stable, descriptive names
let started_at = zart::now("started-at").await?;
let user_tz = zart::capture("user-tz", || env::var("TZ").unwrap_or_default()).await?;
// ❌ Bad — names that change between runs
let val = zart::capture(&format!("value-{}", data.id), || compute()).await?;

If you need to capture a value per iteration in a loop, include the loop index in the name — see Durable Loops for patterns.

Duplicate capture names return an error — this protects against accidentally reusing a name and getting stale data:

let a = zart::capture("my-value", || 42).await?;
let b = zart::capture("my-value", || 99).await?; // StepError: duplicate capture name

During step-mode replay, if a capture is not found in the database (e.g., the step name was changed after the execution started), an error is returned. This means captures added to an existing execution should not be renamed or removed.

  • Durable Loops — iterate over collections durably
  • ZartStep Trait — full step definitions with retries and timeouts
  • Macros — ergonomic proc-macros for durable workflows