docs/reference/rust-review-checklist.md
This document provides a checklist for reviewing Rust code in the workerd project. It covers style guidelines, common pitfalls, and critical patterns to watch for when ensuring code quality.
See ../../src/rust/jsg/README.md for additional context.
All Rust/C++ interop uses the cxx crate. Each crate with a bridge declares
#[cxx::bridge(namespace = "workerd::rust::<crate>")] and has companion ffi.c++/ffi.h files.
type Foo; in CXX bridge) are passed by reference or
Box/UniquePtr.&T must outlive the
Rust reference.workerd::rust::<crate_name> naming convention. The
only exception is python-parser which uses edgeworker::rust::python_parser.ffi.c++ and ffi.h
implementing the C++ side. Generated headers (<file>.rs.h) are produced by the build system.Unsafe code is concentrated at FFI boundaries. Review every unsafe block and unsafe fn:
unsafe block has a // SAFETY: comment explaining which invariants
the caller is responsible for and why they hold at this call site.unsafe fn**.unsafe impl Send / unsafe impl Sync are justified with a comment
explaining why the type is safe to share across threads. jsg::Ref<T> is explicitly not Send
(uses Rc + UnsafeCell internally) — flag any attempt to send it across threads.Local::from_ffi()/into_ffi(), isolate pointer dereference. These are
safe only when the isolate is locked and the handle scope is active.Ref::into_raw() leaks a ref-counted pointer as usize;
Ref::from_raw() reconstructs it. These must be balanced — every into_raw needs exactly one
from_raw to avoid leaks or double-frees.usize via raw pointer, passed through CXX, and
reconstructed on the other side. The closure must be consumed exactly once.dns, net, transpiler) use thiserror for
domain-specific error types. These should implement Display and have descriptive error messages.api, jsg) use jsg::Error with an
ExceptionType variant (e.g., TypeError, RangeError). Domain errors should implement
From<DomainError> for jsg::Error for ergonomic ? usage.panic! for expected errors. Panics across FFI are undefined behavior. Use
Result and propagate errors through the CXX bridge. unwrap() / expect() are acceptable in
tests (clippy is configured with allow-unwrap-in-tests = true).TypeError) is not normally a breaking change
unless it removes properties that user code could depend on (e.g., DOMException has code
that TypeError does not).Rust types exposed to JavaScript via the JSG bindings follow these patterns:
_state: jsg::ResourceState is a required field on all resource types. It holds the opaque
pointers used by the C++ JSG layer to wrap/unwrap the Rust object.#[jsg_resource] on the impl block registers the type as a JS-visible resource.#[jsg_method] auto-converts Rust snake_case method names to JavaScript camelCase.
Methods with a receiver (&self/&mut self) are registered as instance methods on the prototype;
methods without a receiver are registered as static methods on the constructor.
Verify the converted name is correct and matches the intended API surface.#[jsg_static_constant] on a const item inside a #[jsg_resource] impl block exposes it
as a read-only numeric constant on both the constructor and prototype (Rust equivalent of
JSG_STATIC_CONSTANT). The name is used as-is (no camelCase conversion).#[jsg_struct] is for value types (passed by value across the JS boundary).#[jsg_oneof] is for union/variant types (mapped from JS values by trying each variant).jsg::Number wraps JS numbers (distinct from f64). Vec<u8> maps to
Uint8Array, not a regular JS Array. Option<T> maps to nullable. String/&str map to
JS strings.just clippy <crate> on all
Rust changes.#[expect(clippy::...)] over #[allow(clippy::...)] — expect is stricter: it warns if
the suppressed lint no longer fires, preventing stale suppressions from accumulating.use per import line, grouped as std / external / crate (enforced
by rustfmt.toml). Run just format to auto-fix.jsg_test::Harness): creates a V8 isolate, runs Rust code in a real V8
context via run_in_context(|lock, ctx| { ... }). Used for testing JSG resources..c++ test files using kj_test().
These test the C++ side of the FFI bridge.#[cfg(test)] modules: standard Rust unit tests for pure-Rust logic.RUST_BACKTRACE=1 and RUST_TEST_THREADS=1 (serial execution).When reviewing Rust code in workerd, check for each of these items.
#[must_use]. Functions returning Result, Option, or expensive-to-compute values
that callers should not silently discard.bool function parameters. Prefer an enum or options struct for clarity at call
sites. E.g., fn connect(secure: bool) should be fn connect(mode: SecureMode) or take an
options struct.const fn / const for compile-time evaluable functions or constants where
appropriate.jsg or kj Rust crates, or in well-known ecosystem crates already vendored by the project.static mut. Use OnceLock, LazyLock, or other safe synchronization
primitives where global state is genuinely needed.// SAFETY: comments on unsafe.unsafe impl Send/Sync uses..clone()/copies where borrowing or moving would suffice.String where &str works, or Vec<T> where a slice would do.#[allow(...)] is used where #[expect(...)] would work.
Prefer expect to prevent stale suppressions..rs file must begin with the project
copyright/license header using the current year. Expected format:
// Copyright (c) <current-year> Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0
2017-2022 in a file created in 2026) or omits
the header entirely.