diff --git a/src/content/changelog/workers/2025-06-19-improved-durable-objects-workers-rs.mdx b/src/content/changelog/workers/2025-06-19-improved-durable-objects-workers-rs.mdx new file mode 100644 index 000000000000000..b1867c131c71d24 --- /dev/null +++ b/src/content/changelog/workers/2025-06-19-improved-durable-objects-workers-rs.mdx @@ -0,0 +1,134 @@ +--- +title: Improved Durable Objects in Rust Workers, including SQLite Support +description: Rust Workers now have improved Durable Object concurrency and developer experience improvements along with SQLite persistence support. +products: + - workers + - durable-objects +date: 2025-05-19T12:00:00Z +--- + +import { WranglerConfig } from "~/components"; + +Updated support for Durable Objects on [workers-rs](https://github.com/cloudflare/workers-rs) now features [SQLite storage](/durable-objects/best-practices/access-durable-objects-storage/), bringing persistent relational database capabilities directly to your Rust Workers. + +### Breaking Change: Updated Durable Object Macro + +The updated macro no longer needs `async_trait` imports or attributes on `impl` blocks, with better IDE support and allowing multiple Durable Objects in the same file. + +Durable Object trait methods now use `&self` instead of `&mut self`, eliminating the "recursive use of an object detected" errors when handling multiple requests. Use `RefCell` or Tokio's `Mutex` for field-level mutability as needed. + +Before: + +```rust +use worker::{durable_object, DurableObject State, Env, Request, Response, Result}; + +#[durable_object] +struct MyObject { + state: State, +} + +#[durable_object] +impl DurableObject for MyObject { + fn new(state: State, env: Env) -> Self { + Self { state } + } + + async fn fetch(&mut self, request: Request) -> Result { + Response::ok("Hello from Durable Object!") + } +} +``` + +After: + +```rust +use worker::{durable_object, State, Env, Request, Response, Result}; + +#[durable_object] +struct MyObject { + state: State, +} + +impl DurableObject for MyObject { + fn new(state: State, env: Env) -> Self { + Self { state } + } + + async fn fetch(&self, request: Request) -> Result { + Response::ok("Hello from Durable Object!") + } +} +``` + +## Using SQLite Storage in Durable Objects + +Enable SQLite as the Durable Object storage backend, then access to the SQLite API is available through `state.storage().sql()`: + +```rust +use worker::{durable_object, SqlStorage, State, Env, Request, Response, Result}; + +#[durable_object] +pub struct SqlCounter { + sql: SqlStorage, +} + +impl DurableObject for SqlCounter { + fn new(state: State, _env: Env) -> Self { + let sql = state.storage().sql(); + + // Create table if it does not exist + sql.exec("CREATE TABLE IF NOT EXISTS counter(value INTEGER);", None) + .expect("create table"); + + Self { sql } + } + + async fn fetch(&self, _req: Request) -> Result { + #[derive(serde::Deserialize)] + struct Row { + value: i32, + } + + // Read current value + let rows: Vec = self + .sql + .exec("SELECT value FROM counter LIMIT 1;", None)? + .to_array()?; + + let current = rows.get(0).map(|r| r.value).unwrap_or(0); + let next = current + 1; + + // Update counter + self.sql.exec("DELETE FROM counter;", None)?; + self.sql + .exec("INSERT INTO counter(value) VALUES (?);", vec![next.into()])?; + + Response::ok(format!("SQL counter is now {}", next)) + } +} +``` + +Configure your `wrangler.toml` to enable SQLite storage: + + + +```toml +[durable_objects] +bindings = [ + { name = "SQL_COUNTER", class_name = "SqlCounter" } +] + +[[migrations]] +tag = "v1" # Should be unique for each entry +new_sqlite_classes = ["SqlCounter"] # Use new_sqlite_classes for SQLite-enabled objects +``` + + + +Test your new SQLite-powered Durable Object locally by running: + +```bash +npx wrangler dev +``` + +Many thanks to our community contributors. For further details, see the workers-rs [0.6.0 release notes](https://github.com/cloudflare/workers-rs/releases/tag/v0.6.0).