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.
What Problem Do They Solve?
Section titled “What Problem Do They Solve?”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?;How Capture Works
Section titled “How Capture Works”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 databaselet started_at = zart::capture("started-at", || chrono::Utc::now()).await?;The zart::now() Helper
Section titled “The zart::now() Helper”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?.
Full Example
Section titled “Full Example”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(), }) }}When to Use Capture
Section titled “When to Use Capture”| Scenario | Use 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.
Step Name Rules
Section titled “Step Name Rules”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 nameslet 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 runslet 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.
Error Handling
Section titled “Error Handling”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 nameDuring 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.
Next Steps
Section titled “Next Steps”- Durable Loops — iterate over collections durably
- ZartStep Trait — full step definitions with retries and timeouts
- Macros — ergonomic proc-macros for durable workflows