Skip to content

Releases: restatedev/sdk-rust

v0.11.0

Choose a tag to compare

@slinkydeveloper slinkydeveloper released this 21 Jul 09:48

Release v0.11.0

New Features

(Preview) Scope and limit key to enable flow control

This release adds the SDK API for flow control, the new Restate 1.7 feature to cap how many invocations run concurrently within a scope, with optional hierarchical limit keys. Use it to bound cost (especially for AI agents, where every concurrent invocation is real model/API spend), shield downstream systems from bursts, and share capacity fairly across tenants.

To use scope and limit keys in service to service communication:

// Route a call into a named scope
ctx.service_client::<MyServiceClient>()
    .process(payload)
    .scope("tenant-123")
    .call()
    .await?;

// Add a limit key for hierarchical concurrency limits within the scope
ctx.workflow_client::<MyWorkflowClient>("wf-key")
    .run(input)
    .scope("tenant-123")
    .limit_key("api-key/user42")
    .call()
    .await?;

Check out the flow control documentation for more details on how concurrency limits work and how to set them up.

NOTE: This API is in preview and is not enabled by default. To use it in restate-server 1.7, enable the flow control and protocol v7 experimental features via RESTATE_EXPERIMENTAL_ENABLE_PROTOCOL_V7=true and RESTATE_EXPERIMENTAL_ENABLE_VQUEUES=true (new clusters only). See https://docs.restate.dev/services/flow-control for details.

Signals and the InvocationHandle API

The Rust SDK gains signals, alongside a redesigned InvocationHandle API.

Signals let invocations communicate directly with each other: one invocation waits for a signal, and any other handler can resolve or reject it by referencing the target invocation's ID.

// Inside the invocation that wants to wait:
let approved: bool = ctx.signal::<bool>("approved").await?;

// Inside another handler that knows the target invocation's ID:
let target = ctx.invocation_handle(target_invocation_id);
target.signal("approved").resolve(true);
// or, to wake the waiter with a terminal error:
target.signal("approved").reject(TerminalError::new("Request denied"));

New API to define services, objects and workflows

Services, virtual objects and workflows are now defined by putting #[restate_sdk::service] (or #[object]/#[workflow]) on the impl block of a plain struct, with #[handler] on each method.

No more separate trait + impl, no more .serve().

Before:

#[restate_sdk::service]
trait Greeter {
    async fn greet(name: String) -> HandlerResult<String>;
}

struct GreeterImpl;

impl Greeter for GreeterImpl {
    async fn greet(&self, _ctx: Context<'_>, name: String) -> HandlerResult<String> {
        Ok(format!("Greetings {name}"))
    }
}

Endpoint::builder().bind(GreeterImpl.serve()).build();

After:

struct Greeter;

#[restate_sdk::service]
impl Greeter {
    #[handler]
    async fn greet(&self, _ctx: Context<'_>, name: String) -> HandlerResult<String> {
        Ok(format!("Greetings {name}"))
    }
}

Endpoint::builder().bind(Greeter).build();

For virtual objects and workflows, whether a handler is shared or exclusive is inferred directly from the context type in its signature (ObjectContext/SharedObjectContext, WorkflowContext/SharedWorkflowContext):

struct Counter;

#[restate_sdk::object]
impl Counter {
    #[handler]
    async fn add(&self, ctx: ObjectContext<'_>, val: u64) -> HandlerResult<u64> {
        let new = ctx.get::<u64>("count").await?.unwrap_or(0) + val;
        ctx.set("count", new);
        Ok(new)
    }

    #[handler]
    async fn get(&self, ctx: SharedObjectContext<'_>) -> HandlerResult<u64> {
        Ok(ctx.get::<u64>("count").await?.unwrap_or(0))
    }
}

The new API supports overriding service/handler names and client visibility, and fully generic services (type params, bounds, where-clauses):

#[restate_sdk::service(name = "greeter", client_visibility = "pub(crate)")]
impl Greeter {
    #[handler(name = "myGreet")]
    async fn my_greet(&self, _ctx: Context<'_>, name: String) -> HandlerResult<String> {
        Ok(name)
    }
}

Together with this change, you can now express service and handler level configuration directly in the macro arguments:

struct MyService;

#[restate_sdk::service(
    inactivity_timeout = "1m",
    idempotency_retention = "1 day",
    journal_retention = "3 days",
    invocation_retry_policy(
        initial_interval = "100ms",
        max_interval = "10s",
        factor = 2.0,
        max_attempts = 10,
        on_max_attempts = "pause"
    )
)]
impl MyService {
    #[handler(journal_retention = "1h", lazy_state)]
    async fn my_handler(&self, ctx: Context<'_>) -> HandlerResult<()> {
        // ...
        Ok(())
    }
}

See the configuration module docs for the complete reference.

NOTE: The old trait-based definition API still works but is deprecated and will emit a warning; please migrate to the struct-based API above.

Durable map / map_ok / map_err combinators

DurableFuture gains map, map_ok and map_err. Unlike futures' FutureExt::map / TryFutureExt::map_ok, these preserve the durable future type, so the mapped result is still a DurableFuture and can keep being used in select! and in DurableFuturesUnordered.

use restate_sdk::prelude::*;
use restate_sdk::context::DurableFuture; // brings map / map_ok / map_err into scope

async fn example(ctx: Context<'_>) -> Result<(), TerminalError> {
    // map: transform the whole Output. Here Result<(), _> becomes String, so no `?` needed.
    let outcome: String = ctx
        .sleep(std::time::Duration::from_secs(1))
        .map(|res| match res {
            Ok(())  => "woke up".to_owned(),
            Err(e)  => format!("cancelled: {e}"),
        })
        .await;

    // map_ok: transform only the success value; a terminal error passes through untouched.
    let len: usize = ctx.signal::<String>("count").map_ok(|s| s.len()).await?;

    // map_err: transform only the terminal error; the success value passes through untouched.
    let token: String = ctx
        .signal::<String>("token")
        .map_err(|e| TerminalError::new(format!("token signal failed: {}", e.message())))
        .await?;

    let _ = (outcome, len, token);
    Ok(())
}

Turn any error into a terminal error with .terminal()

A new TerminalErrorExt trait provides the shorthand to convert Result error value to terminal error using .terminal():

use restate_sdk::prelude::*;

async fn handle() -> Result<(), HandlerError> {
    let parsed: i32 = "not a number".parse().terminal()?;
    Ok(())
}

attach() and the redesigned InvocationHandle

InvocationHandle was redesigned to hold attach() and the new signal() handle.

You can obtain one when you already know an invocation id (ctx.invocation_handle(id)), by .await-ing the handle returned from a one-way send(), or from a call() future:

let handle = ctx.invocation_handle(invocation_id);

// Known synchronously, no await:
let id: &str = handle.invocation_id();

// Await the invocation's output/result:
let output: MyOutput = handle.attach::<MyOutput>().await?;

// Fire-and-forget cancellation:
handle.cancel();

This is a breaking change to the previous InvocationHandle trait and their usage:

  • invocation_id() is now a synchronous -> &str (was async -> Result<String, _>).
  • cancel() is now a synchronous fire-and-forget (was async).
  • CallFuture doesn't implement InvocationHandle anymore, now you need to call invocation_handle(&self).
  • send() / send_after() now return an awaitable SendHandle: .await-ing it yields an InvocationHandle to the new invocation.

Additional changes

  • Re-exported the rand and uuid crates (#123).
  • Bumped the shared e2e test suite to restatedev/e2e/sdk-tests@v2.2 and conformed test-services to the v2.0/v2.2 contract (#124).
  • CI now reuses a prebuilt test-services image: integration.yaml accepts a serviceImage input, and a new docker.yaml builds and pushes ghcr.io/restatedev/test-services-rust (#124).

Full Changelog: v0.10.0...v0.11.0

v0.10.0

Choose a tag to compare

@slinkydeveloper slinkydeveloper released this 28 Apr 06:33

New Features

DurableFuturesUnordered

Push a dynamic number of durable futures and consume them in completion order. Each result comes back with the index of the future that completed, so you can correlate it with your input.

let mut futures = DurableFuturesUnordered::new();
futures.push(ctx.sleep(Duration::from_secs(1)));
futures.push(ctx.sleep(Duration::from_secs(2)));
futures.push(ctx.sleep(Duration::from_secs(3)));

while let Some((index, result)) = futures.next().await? {
    result?;
    // ...
}

PR #106

Selectable jsonwebtoken crypto backend

You can now pick jsonwebtoken's backend explicitly, either rust_crypto or aws_lc_rs:

restate-sdk = { version = "0.10", default-features = false, features = ["http_server", "aws_lc_rs"] }

rust_crypto remains the default. Disabling both lets you call jsonwebtoken::crypto::CryptoProvider::install_default() yourself.

PR #108

Full Changelog: v0.9.0...v0.10.0

v0.9.0

Choose a tag to compare

@tillrohrmann tillrohrmann released this 20 Feb 09:03
3dfa923

✨ Highlights

  • 🔧 MSRV raised to 1.90.0 — a downstream dependency silently raised its MSRV, which would cause build failures for users relying on our previously announced MSRV
  • 🏷️ #[lazy_state] handler attribute — opt into lazy state loading on a per-handler basis

⚠️ Breaking Changes

Dependency bumps with user-facing API changes

Several dependencies have been bumped. Most are transparent, but the following affect user code:

rand 0.9 → 0.10

The rand() method on context types returns &mut rand::prelude::StdRng. In rand 0.10, the trait that provides convenience methods like .random() and .random_range() was renamed from Rng to RngExt. If you import this trait, update your code:

- use rand::Rng;
+ use rand::RngExt;

Additionally, the low-level trait RngCore (providing next_u32(), next_u64(), fill_bytes()) was renamed to Rng:

- use rand::RngCore;
+ use rand::Rng;

StdRng no longer implements Clone. If you were cloning the RNG returned by ctx.rand(), this is no longer possible.

Other dependency bumps (no user-facing changes)

The following dependencies were also bumped but require no changes to user code:

Dependency Old New
restate-sdk-shared-core 0.6.0 0.9.0
aws_lambda_events 0.16.1 1.0
lambda_runtime 0.14.2 1.0
typify 0.1.0 0.6
jsonptr 0.5.1 0.7
regress 0.10.3 0.10
bytes 1.10 1.11
http 1.3 1.4
hyper 1.6 1.8
tokio 1.44 1.49
uuid 1.16.0 1.20
schemars 1.0.0 1.2
reqwest (dev) 0.12 0.13
testcontainers 0.23.3 0.27

MSRV raised to 1.90.0

The minimum supported Rust version (MSRV) has been raised from 1.85.0 to 1.90.0.

This bump was necessary because a downstream dependency raised its own MSRV without announcing it. Without this change, users relying on our previously announced MSRV of 1.85.0 would encounter build failures. Any user building with a toolchain between 1.85.0 and 1.87.x is affected. Rather than pinning to an older version of the dependency, we chose to follow the ecosystem forward.

Impact on Users:

  • Projects using a Rust toolchain older than 1.90.0 will fail to compile against this version of the SDK
  • Your own crate's edition field does not need to change, but your installed Rust toolchain must be >= 1.90.0

Migration Guidance:

Update your Rust toolchain:

rustup update stable

Verify your version:

rustc --version  # Must be >= 1.90.0

🆕 New Features

#[lazy_state] handler attribute

You can now annotate individual handlers with #[lazy_state] to enable lazy state loading for that handler. When enabled, state is fetched on demand rather than eagerly loaded when the handler is invoked.

Example:

#[restate_sdk::object]
pub trait MyObject {
    #[lazy_state]
    async fn my_handler(name: String) -> Result<String, HandlerError>;
}

Related Issues:


📄 Full release notes

v0.8.0

Choose a tag to compare

@tillrohrmann tillrohrmann released this 12 Feb 08:43
66e03b1

✨ Highlights

  • 🔧 MSRV raised to 1.85.0 with Rust edition 2024 — ensure your toolchain is up to date
  • 🆔 Invocation ID now accessible from all handler context types via invocation_id()
  • 🔓 ProtocolMode and HandleOptions are now public — enabling custom server integrations beyond the built-in HTTP server and Lambda endpoint

⚠️ Breaking Changes

Rust edition 2024 and MSRV 1.85.0

The SDK now uses Rust edition 2024 and requires a minimum supported Rust version (MSRV) of 1.85.0 (previously 1.76.0).

Impact on Users:

  • Projects using an older Rust toolchain will fail to compile against this version of the SDK
  • Your own crate's edition field does not need to change, but your installed Rust toolchain must be >= 1.85.0

Migration Guidance:

Update your Rust toolchain:

rustup update stable

Verify your version:

rustc --version  # Must be >= 1.85.0

Related Issues:

🚀 New Features

🆔 Invocation ID available in handler context

All handler context types now expose an invocation_id() method that returns the current Restate invocation ID as a &str.

This is useful for logging, debugging, and correlating requests across services.

Example:

impl Greeter for GreeterImpl {
    async fn greet(&self, ctx: Context<'_>, name: String) -> HandlerResult<String> {
        let id = ctx.invocation_id();
        tracing::info!("Handling invocation {id} for {name}");
        Ok(format!("Greetings {name}"))
    }
}

Available on all context types: Context, ObjectContext, SharedObjectContext, WorkflowContext, and SharedWorkflowContext.

Related Issues:

🔓 Public ProtocolMode, HandleOptions, and handle_with_options

ProtocolMode, HandleOptions, and the Endpoint::handle_with_options() method are now part of the public API (exported via the prelude).

This enables building custom server integrations beyond the built-in HttpServer and LambdaEndpoint. For example, you can now implement your own HTTP framework adapter or request-response transport by calling handle_with_options directly with the desired ProtocolMode.

Related Issues:


📄 Full release notes

👋 New Contributors

Full Changelog: v0.7.0...v0.8.0

v0.7.0

Choose a tag to compare

@slinkydeveloper slinkydeveloper released this 16 Sep 12:49

AWS Lambda support

You can now use the Rust SDK with AWS Lambda, check out https://github.com/restatedev/sdk-rust?tab=readme-ov-file#running-on-lambda for more details on how to set it up.

Invocation retry policy

When used with Restate 1.5, you can now configure the invocation retry policy from the SDK directly. See https://github.com/restatedev/restate/releases/tag/v1.5.0 for more details on the new invocation retry policy configuration.

What's Changed

New Contributors

Full Changelog: v0.6.0...v0.7.0

v0.6.0

Choose a tag to compare

@slinkydeveloper slinkydeveloper released this 02 Jul 18:48

Introduce new feature bind_with_options, allowing to customize service and handler configuration options. Please note, this feature works only with Restate 1.4 onward.

What's Changed

Full Changelog: v0.5.0...v0.6.0

v0.5.0

Choose a tag to compare

@slinkydeveloper slinkydeveloper released this 29 Apr 13:52

What's Changed

Schema generation

By enabling the optional dependency schemars, the SDK will generate and propagate JSON schemas of your handlers input/output, which will be available in the generated OpenAPI and in the Restate Playground. For more details on the schema generation, and info on how to tune it, check the https://docs.rs/restate-sdk/latest/restate_sdk/serde/trait.PayloadMetadata.html documentation.

Thanks to @hxphsts for the contribution!

New restate-sdk-testcontainers module

We've released a first version of the testcontainers integration, that simplifies testing locally your restate service. Check https://github.com/restatedev/sdk-rust/blob/main/testcontainers/tests/test_container.rs for a full example.

Thanks to @kpwebb for the contribution.

Full Changelog: v0.4.0...v0.5.0

v0.4.0

Choose a tag to compare

@slinkydeveloper slinkydeveloper released this 09 Apr 17:08

We're pleased to announce the release of Rust SDK 0.4.0, in combination with Restate 1.3.
Check out the announcement blog post for more details about Restate 1.3 and the new SDK features: https://restate.dev/blog/announcing-restate-1.3/

This SDK introduces the following new APIs:

Rust SDK 0.4.0 can be used in combination with Restate 1.3 onward.

Full changelog

New Contributors

Full Changelog: v0.3.2...v0.4.0

v0.3.2

Choose a tag to compare

@slinkydeveloper slinkydeveloper released this 05 Dec 10:07

What's Changed

New Contributors

Full Changelog: v0.3.0...v0.3.2

v0.3.0

Choose a tag to compare

@slinkydeveloper slinkydeveloper released this 10 Sep 07:17

Breaking changes

  • The ctx.run API now does not require name as first parameter anymore
  • Renamed Endpoint.with_service to Endpoint.bind
  • This SDK is compatible only with Restate >= 1.1

New features

What's Changed

Full Changelog: v0.2.0...v0.3.0