Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/bonk/specialists/rust-first.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ paths:
- src/**/ffi.h
- src/**/bridge.h
- src/**/cxx-bridge.h
- src/workerd/server/cli-main.*
- src/workerd/server/factory/bootstrap.*
- src/workerd/server/config-compiler.*
- src/workerd/util/setup-async-io.*
budget: 4m
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,16 +162,15 @@ Be aware that workerd uses tcmalloc for memory allocation in the typical case. W
- **`jsg/`** - JavaScript Glue layer for V8 integration
- Core JavaScript engine bindings and type wrappers
- Promise handling, memory management, module system
- **`server/`** - Main server implementation and configuration
- Main binary entry point and Cap'n Proto config handling
- **`server/`** - The `workerd` binary: the Rust command line (`cli/`) and server (`server/`) over the C++ worker factory (`factory/`), plus the Cap'n Proto config schema; see `src/workerd/server/AGENTS.md`
- **`util/`** - Utility libraries (SQLite, UUID, threading, etc.)

### Multi-Language Support

- **`src/cloudflare/`** - Cloudflare-specific APIs (TypeScript)
- **`src/node/`** - Node.js compatibility layer (TypeScript)
- **`src/pyodide/`** - Python runtime support via Pyodide
- **`src/rust/`** - Rust integration components; see `src/rust/AGENTS.md` for the full macro reference and GC tracing guide
- **`src/rust/`** - Rust crates without a C++ home of their own and the in-tree cxx fork; see `src/rust/AGENTS.md` for the full macro reference and GC tracing guide (component crates such as the server live beside their C++)

### Configuration System

Expand All @@ -190,6 +189,7 @@ Be aware that workerd uses tcmalloc for memory allocation in the typical case. W
| Modify compat flags | `src/workerd/io/compatibility-date.capnp` | ~1400 lines; annotations define flag names + enable dates |
| Add autogate | `src/workerd/util/autogate.h` | Add key to WORKERD_AUTOGATES macro; kebab-case name auto-derived; see header comment |
| Config schema | `src/workerd/server/workerd.capnp` | Cap'n Proto; capability-based security |
| Server / binary | `src/workerd/server/` | Rust `workerd-server` crate (`server/`) drives the C++ `worker-factory`; see `server/AGENTS.md` |
| Worker lifecycle | `src/workerd/io/worker.{h,c++}` | Isolate, Script, Worker, Actor classes |
| Request lifecycle | `src/workerd/io/io-context.{h,c++}` | IoContext: the per-request god object |
| Coroutine cancellation | `docs/reference/detail/async-patterns.md` | `CURRENT_INVOCATION` with `KJ_DEFER`; `KJ_ON_SCOPE_FAILURE` is exception-only |
Expand Down
8 changes: 6 additions & 2 deletions build/wd_rust_crate.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def wd_rust_crate(
cxx_bridge_tags = [],
cxx_bridge_local_defines = [],
cxx_bridge_features = [],
cxx_bridge_visibility = [],
testonly = False,
visibility = None):
"""Define rust crate.
Expand Down Expand Up @@ -104,6 +105,8 @@ def wd_rust_crate(
cxx_bridge_deps: either a flat dependency list applied to every bridge source, or a dict of
bridge source => dependency list.
cxx_bridge_hdrs: headers the bridges include!(); defaults to every .h file in the package.
cxx_bridge_visibility: visibility of the generated <bridge>@cxx libraries, for C++ in
another package that includes a bridge's header (it must also link the crate).
testonly: True for a crate that only tests depend on (a test harness, the Rust half of a
C++ test). Like other test code, it is not held to //build/rust:lints.
"""
Expand Down Expand Up @@ -144,8 +147,9 @@ def wd_rust_crate(
hdrs = hdrs,
include_prefix = include_prefix,
strip_include_prefix = "",
# Not applying visibility here – if you import the cxxbridge header, you will likely
# also need the rust library itself to avoid linker errors.
# Private by default: a C++ library that includes the bridge header also needs the
# crate itself at link time.
visibility = cxx_bridge_visibility,
deps = cxx_bridge_deps.get(bridge_src, []) + [
"//src/rust/cxx/kj-rs",
"//src/rust/cxx:cxx",
Expand Down
2 changes: 2 additions & 0 deletions clippy.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ allow-unwrap-in-tests = true
allow-expect-in-tests = true
allow-panic-in-tests = true
max-fn-params-bools = 2
# Proper nouns beyond clippy's default list, so doc comments need not backtick them.
doc-valid-idents = ["SQLite", "WebSockets", ".."]
18 changes: 18 additions & 0 deletions deps/rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions deps/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ libz-rs-sys = { version = "0.6", default-features = false, features = ["std", "r
futures = "0"
http = "1"
http-body = "1"
# The server's disk service: `Last-Modified` dates, and URL paths, parsed and percent-decoded.
httpdate = "1"
percent-encoding = "2"
url = "2"
# The server's CryptoKey bindings: hex key material.
data-encoding = "2"
# `workerd test`: the service and entrypoint filters.
glob = "0"
# The server's `network` services: the `allow` / `deny` CIDR ranges.
ipnet = "2"
hyper = { version = "1", default-features = false, features = ["client", "http1", "server"] }
# The pooled HTTP/1.1 client (kj-hyper/client.rs) is hyper-util's legacy client over a connector
# kj-hyper supplies; tower-service is the trait that connector implements.
Expand Down
24 changes: 11 additions & 13 deletions docs/jsg.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,25 +345,23 @@ int main() {

### Real-World Reference: workerd Initialization

For a production example, see how workerd initializes V8 in `src/workerd/server/cli-main.c++`:
For a production example, see how workerd initializes V8 in `src/workerd/server/factory/bootstrap.c++`:

```cpp
// From cli-main.c++ serveImpl()
auto platform = jsg::defaultPlatform(0);
WorkerdPlatform v8Platform(*platform);
jsg::V8System v8System(v8Platform,
KJ_MAP(flag, config.getV8Flags()) -> kj::StringPtr { return flag; },
platform.get());
// From bootstrap.c++ (Bootstrap)
platform = jsg::defaultPlatform(0);
v8Platform = kj::heap<WorkerdPlatform>(*platform);
v8System = kj::heap<jsg::V8System>(*v8Platform,
KJ_MAP(flag, config.getV8Flags()) -> kj::StringPtr { return flag; }, platform.get());
```

And how isolates are created in `src/workerd/server/server.c++`:
And how isolates are created in `src/workerd/server/factory/worker-factory.c++`:

```cpp
// From server.c++ when creating a worker
auto isolateGroup = v8::IsolateGroup::GetDefault();
auto api = kj::heap<WorkerdApi>(globalContext->v8System, def.featureFlags, extensions,
limitEnforcer->getCreateParams(), isolateGroup, kj::mv(jsgobserver),
*memoryCacheProvider, pythonConfig);
// From worker-factory.c++ when compiling a worker
auto api = kj::heap<WorkerdApi>(factory.v8System, featureFlags, extensions,
limitEnforcer->getCreateParams(), v8::IsolateGroup::GetDefault(), kj::mv(jsgobserver),
*factory.memoryCacheProvider, options.pythonConfig, kj::mv(listeners));
```

---
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/detail/new-module-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,7 @@ if (isolate.isUsingNewModuleRegistry()) {
The isolate-level bit is set at isolate construction from
`isNewModuleRegistryEnabled(flags)` (`io/features.h`), which returns false for
Python workers regardless of the `new_module_registry` flag. All other
flag-check sites (`server.c++` registry creation, `worker.c++` compile paths and
flag-check sites (`worker-factory.c++` registry creation, `worker.c++` compile paths and
the nodejs_compat_v2 process/buffer warm-up, and the api-level require paths)
route through the same function, so a worker can never be split across the two
registries. Note that `Cloudflare.compatibilityFlags.new_module_registry` as
Expand Down
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ test-compile-flags:
exit 1
fi
just _clangd-check "src/workerd/server/server.c++"
just _clangd-check "src/workerd/server/factory/worker-factory.c++"
just _clangd-check "src/workerd/server/workerd-api.c++"
CLANGD := "clangd"
Expand Down
5 changes: 3 additions & 2 deletions src/rust/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

A dozen or so Rust crates — mostly libraries, plus the `gen-compile-cache` binary — linked into workerd via CXX FFI. No Cargo workspace — entirely Bazel-driven (`wd_rust_crate.bzl` / `wd_rust_binary.bzl`). Clippy pedantic+nursery enabled; `allow-unwrap-in-tests`; `clippy.toml` and `rustfmt.toml` live at the repository root and apply to every crate.

Rust does not have to live here. A crate that belongs to a component lives beside that component's C++, in the same package, in a subdirectory named for the crate: e.g. workerd's entry point (`workerd-cli`, `src/workerd/server/cli/`, next to `cli-main.c++`). Such crates glob their `srcs` from that subdirectory, list `cxx_bridge_hdrs` explicitly, and use the component's C++ namespace for their bridge. This directory holds the crates that have no C++ home of their own, and the in-tree cxx fork.
Rust does not have to live here. A crate that belongs to a component lives beside that component's C++, in a subdirectory named for the crate: e.g. workerd's entry point (`workerd-cli`, `src/workerd/server/cli/`, declared in `src/workerd/server/BUILD.bazel` with its `srcs` globbed from that subdirectory and its `cxx_bridge_hdrs` listed explicitly) and its server (`workerd-server`, `src/workerd/server/server/`, a package of its own over the C++ worker factory in `src/workerd/server/factory/`; see `src/workerd/server/AGENTS.md`). Such crates use the component's C++ namespace for their bridge (`workerd::server`, `workerd::server::cli`), and their generated bridge header sits at the crate's C++ include path (`workerd/server/cli/bridge.rs.h`, `workerd/server/server/bridge.rs.h`). This directory holds the crates that have no C++ home of their own, and the in-tree cxx fork.

> **The "CXX FFI" above is the in-tree `src/rust/cxx` fork of [cxx-rs](https://cxx.rs/)** — not stock cxx-rs. It adds deep KJ interoperability upstream lacks: `async` fns become `kj::Promise<T>`, you can return/hold `kj::Own<T>`, `Result<T>` throws `kj::Exception`, and other KJ types cross the boundary (see CXX BRIDGE below). cxx-rs is well represented in LLM training data, so it is easy to "recall" an API that is wrong here — prefer the prior art in these crates and the in-tree CXX sources (especially its `kj-rs` crate) over upstream cxx-rs docs or memory.

Expand All @@ -22,7 +22,7 @@ _Snapshot — the set drifts as crates come and go; `bazel query //src/rust/...`
| `kj/` | Rust bindings for KJ library (`http`, `io`, `own` submodules); `Result<T>` = `Result<T, cxx::KjError>` |
| `cxx/kj-hyper/` | HTTP/1.1, WebSocket and TLS for the Rust server (hyper, rustls; WebSockets are kj's over the upgraded transport) with kj-typed seams: `server::serve_connection` dispatches to a `Handler` with kj arguments, `client::Client` implements `kj::http::Service`; see `src/rust/cxx/AGENTS.md` |
| `worker/` | Rust counterpart of `workerd::WorkerInterface`: the `worker::Interface` trait (`into_kj` hands an implementation to C++ as `KjOwn<WorkerInterface>`; `not_supported` answers a `CustomEvent` as C++'s `event->notSupported()` does), `PromisedInterface` (an `Interface` whose target is still starting, as C++'s `PromisedWorkerInterface`) plus FFI bindings; multi-bridge crate |
| `cxx-integration/` | Tokio runtime init; called from C++ `main()` before anything else |
| `cxx-integration/` | One bridge function, `trigger_panic`: the hook C++ tests use to exercise the panic-to-`kj::Exception` conversion at the bridge |
| `cxx-integration-test/` | Non-production crate exercising Rust/C++ integration: callbacks, shared structs, `Result` error mapping |
| `transpiler/` | TS type stripping via SWC (`ts_strip()`, `StripOnly` mode) |
| `python-parser/` | Python import extraction via `ruff_python_parser`; **namespace: `edgeworker::rust::`** |
Expand All @@ -48,6 +48,7 @@ _Snapshot — the set drifts as crates come and go; `bazel query //src/rust/...`
- The crates derived from upstream cxx (`src/rust/cxx/{src,syntax,gen,macro,tests}`) are outside these defaults; the `kj-rs*` crates beside them are inside.
- **Tests**: a module's unit tests live beside it in `<module>-test.rs` (`dns.rs` → `dns-test.rs`, `lib.rs` → `lib-test.rs`), never in an inline `mod tests { ... }`, so a diff shows production changes and test changes as separate files. See UNIT TEST FILES below. JSG tests use `jsg_test::Harness::run_in_context()`. Always run the full `src/rust/...` test suite (`bazel test //src/rust/...`) rather than targeting a single crate — changes in shared crates like `jsg` or `jsg-macros` can break downstream consumers
- **FFI pointers**: functions receiving raw pointers must be `unsafe fn` (see `jsg/README.md`)
- **`KjOwn<T>`**: dropping one runs `kj::Own<T>::~Own()` through a C++ function generated per type, so `T: kj_rs::OwnTarget` is implemented by the bridge that declares `T` (`type T;`) and holds it in a `KjOwn`. A bridge that only aliases `T` gets none; add `impl KjOwn<T> {}` to the declaring bridge. Details in `cxx/kj-rs/README.md` and `cxx/AGENTS.md`
- **Parameter ordering**: `&Lock` / `&mut Lock` must always be the first parameter in any function that takes a lock (matching the C++ convention where `jsg::Lock&` is always first). This applies to free functions, trait methods, and associated functions (excluding `&self`/`&mut self` receivers which come before `lock`).
- **Method naming**: do not use `get_` prefixes on methods — e.g. `buf.backing_store()` not `buf.get_backing_store()`. Static constructors belong on the marker struct (`impl ArrayBuffer { fn new(...) }`) not on `impl Local<'_, ArrayBuffer>`.
- **FFI naming**: instance methods on an existing handle use a `local_<type>_<method>` prefix (e.g. `local_array_buffer_byte_length`). Static constructors that create a new value do **not** use the `local_` prefix — name them `<type>_<method>` (e.g. `array_buffer_new_with_mode`, `array_buffer_maybe_new`, `backing_store_new_resizable`).
Expand Down
10 changes: 6 additions & 4 deletions src/rust/cxx/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ Bazel module, Cargo workspace, toolchain configuration, or external `workerd-cxx
`kj::WebSocket`, `kj::AsyncIoStream`) so requests reach a C++ `WorkerInterface` and C++ can
make outbound requests; see "kj-hyper" below
- `kj-rs-io/` — tokio-backed `kj::AsyncIoStream` / `kj::Network` / `kj::LowLevelAsyncIoProvider`
(the I/O providers for the tokio loop, `kj_rs_io::setupTokioAsyncIo()`), `loopback:` addresses
(in-process connections for `workerd test`), the `--watch` file watcher (Rust over `notify`),
and signals. C++ there is interface adaptation only; the one policy
(the I/O providers for the tokio loop, `kj_rs_io::setupTokioAsyncIo()`), the same addresses and
sockets for a Rust caller (`TokioAddress::parse_str`, then `listen` and `TokioListener::accept`,
`connect_first` or `bind_udp`; `wrap_listener` for an inherited socket; they hand back tokio's
own sockets), and the `--watch` file watcher (Rust over `notify`).
C++ there is interface adaptation only; the one policy
object that stays C++ is `PeerFilter`, a wrapper over KJ's own `kj::_::NetworkFilter`, which
Rust consults through a bridged `should_allow`
the C++ adapters apply in their connect and accept loops
- `tests/` and `kj-rs/tests/` — Rust and C++ bridge integration tests
- `tools/bazel/` — Bazel bridge-generation macro used by this component's tests

Expand Down
Loading
Loading