# JSG (JavaScript Glue) Rust Bindings Rust bindings for the JSG (JavaScript Glue) layer, enabling Rust code to integrate with workerd's JavaScript runtime. ## Core Types ### `Lock` Provides access to V8 operations within an isolate lock. Passed to resource methods and callbacks. ### `Rc` Strong reference to a Rust resource managed by GC. Derefs to `&R`. Cloning and dropping a `Rc` tracks strong references for the garbage collector. ### `Weak` Weak reference that doesn't prevent GC collection. Use `upgrade()` to get a `Rc` if the resource is still alive. ### `Realm` Per-isolate state for Rust resources exposed to JavaScript. Stores cached function templates. ## Resources Rust resources integrate with V8's garbage collector through the existing C++ `Wrappable` infrastructure — the same GC system that C++ `jsg::Rc` and `jsg::Object` use. ```rust use jsg_macros::{jsg_resource, jsg_method}; use std::cell::Cell; #[jsg_resource] struct MyResource { name: String, // jsg::Rc fields — strong GC edges, automatically traced child: jsg::Rc, maybe_child: Option>, nullable_child: jsg::Nullable>, // jsg::Weak fields — weak reference, does not keep the target alive observer: jsg::Weak, // jsg::v8::Global fields — JS value traced with strong↔weak dual-mode switching. // Allows GC to detect and collect back-reference cycles (e.g. a stored callback // that closes over the resource's own JS wrapper). // Must be wrapped in Cell<_> for interior mutability (trace takes &self). callback: Cell>>, } #[jsg_resource] impl MyResource { #[jsg_method] fn get_name(&self) -> Result { Ok(self.name.clone()) } } ``` ### Lifecycle ```rust // Create a resource let resource = jsg::Rc::new(MyResource { ... }); // Convert to a JS object (uses cached FunctionTemplate) let js_obj = resource.to_js(&mut lock); // Convert from JS back to a Ref let r: jsg::Rc = jsg::Rc::from_js(&mut lock, js_val)?; ``` ### GC Behavior - **No JS wrapper**: Dropping the last `Rc` immediately destroys the resource (no GC needed). - **With JS wrapper**: Dropping all `Rc`s makes the wrapper eligible for V8 GC. When collected, the resource is destroyed. - **Tracing**: The `#[jsg_resource]` macro auto-generates `Traced::trace` and calls it on every field: | Field type | Traced? | Notes | |---|---|---| | `jsg::Rc` | Yes — strong edge | Keeps target alive through GC | | `Option>` | Yes — when `Some` | | | `jsg::Nullable>` | Yes — when `Some` | | | `Cell>` | Yes — strong edge | Use `Cell` when field needs interior mutability | | `Cell>>` | Yes — when `Some` | | | `Cell>>` | Yes — when `Some` | | | `Vec>` | Yes — each element | Iterates and visits every `Rc` in the vec | | `HashMap>` | Yes — each value | Iterates `.values()` and visits each `Rc` | | `BTreeMap>` | Yes — each value | Iterates `.values()` and visits each `Rc` | | `HashSet>` | Yes — each element | Iterates and visits every `Rc` | | `BTreeSet>` | Yes — each element | Iterates and visits every `Rc` | | `Cell>>` | Yes — each element | `Cell` variant of above | | `Cell>>` | Yes — each value | `Cell` variant of above | | Same patterns with `jsg::v8::Global` | Yes | All collection forms work with `Global` too | | `jsg::v8::Global` | Yes — dual strong/traced | Enables cycle collection; see below | | `Option>` | Yes — when `Some` | | | `jsg::Nullable>` | Yes — when `Some` | | | `Cell>` | Yes — dual strong/traced | Required when set after construction | | `Cell>>` | Yes — when `Some` | | | `jsg::Weak` | No | Doesn't keep target alive | | Any `T: Traced` | Depends on `T` | `#[jsg_resource]` calls `Traced::trace` on every field | - **`Cell` for interior mutability**: `Traced::trace` takes `&self`. Fields that need to be mutated after construction (e.g. a callback set in a method) can use `Cell`. `Cell` implements `Traced` by reading through `as_ptr()` during single-threaded GC tracing. - **`Traced` drives field tracing**: `#[jsg_resource]` now traces every named field via `Traced::trace(&self.field, visitor)`. Types with no GC edges use no-op `Traced` impls; wrappers/collections delegate recursively. - **Nested wrappers are supported by composition**: `Option>>` works as long as each layer implements `Traced`. - **`#[jsg_resource(custom_trace)]`**: suppresses the generated `Traced` impl so you can write your own. `GarbageCollected` (`memory_name`), `Type`, `ToJS`, and `FromJS` are still generated. - **`jsg::v8::Global` cycle collection**: Uses the same strong↔traced dual-mode as C++ `jsg::V8Ref`. While the parent resource has strong Rust refs the JS handle stays strong. Once all Rust `Rc`s are dropped, `visit_global` downgrades the handle to a `v8::TracedReference` that cppgc can follow — allowing cycles (e.g. a resource holding a callback that captures its own wrapper) to be detected and collected. - **Circular references** through `jsg::Rc` are **not** collected, matching C++ `jsg::Rc` behavior. ## V8 Handle Types ### `Local<'a, T>` A stack-allocated handle to a V8 value. The lifetime `'a` is tied to the `HandleScope` that created it. ```rust let str_value = "hello".to_local(&mut lock); let num_value = 42u32.to_local(&mut lock); let global = local.to_global(&mut lock); ``` ### `Global` A persistent handle that outlives `HandleScope`s. Must be explicitly managed. `Global` fields on `#[jsg_resource]` structs participate in GC tracing when visited via `GcVisitor::visit_global`. This enables the garbage collector to detect and collect back-reference cycles — for example, a resource that stores a JS callback which closes over the resource's own JS wrapper: ```rust #[jsg_resource] struct EventEmitter { // Cell> for interior mutability: the callback is set after // construction, and trace() receives &self. on_event: Cell>>, } #[jsg_resource] impl EventEmitter { #[jsg_method] fn set_callback(&self, lock: &mut jsg::Lock, cb: jsg::v8::Local) { self.on_event.set(Some(cb.to_global(lock))); } } ``` Without tracing, storing a `Global` back to the resource's own wrapper creates an unbreakable reference cycle that leaks until the worker is torn down. With `visit_global` tracing (generated automatically by `#[jsg_resource]`), the cycle is collected by the next full GC after all strong Rust `Rc`s are dropped. ## Union Types To accept JavaScript values that can be one of several types, define an enum with `#[jsg_oneof]`: ```rust use jsg_macros::jsg_oneof; #[jsg_oneof] #[derive(Debug, Clone)] enum StringOrNumber { String(String), Number(f64), } // In a jsg_method: pub fn process(&self, value: StringOrNumber) -> Result { match value { StringOrNumber::String(s) => Ok(format!("string: {}", s)), StringOrNumber::Number(n) => Ok(format!("number: {}", n)), } } ``` This is similar to `kj::OneOf<>` in C++ JSG. ## Feature Flags (Compatibility Flags) `Lock::feature_flags()` provides Rust-native access to the worker's compatibility flags, backed by the Cap'n Proto Rust crate (`capnp`). The flags are deserialized from the `CompatibilityFlags` schema in `src/workerd/io/compatibility-date.capnp`. ### Reading flags ```rust if lock.feature_flags().get_node_js_compat() { // Node.js compatibility behavior } ``` `feature_flags()` returns a capnp-generated `compatibility_flags::Reader` with a getter for each boolean flag (e.g., `get_node_js_compat()`, `get_url_standard()`, `get_fetch_refuses_unknown_protocols()`). ### How it works 1. During worker initialization, C++ canonicalizes the worker's `CompatibilityFlags` via `capnp::canonicalize()` and passes the bytes to `realm_create()`, which parses them once and stores the result in the per-context `Realm`. 2. `lock.feature_flags()` reads the cached `FeatureFlags` and returns its capnp reader. No copies or re-parsing on access. ### Key types and files | Item | Location | |------|----------| | `FeatureFlags` struct | `src/rust/jsg/feature_flags.rs` | | `Lock::feature_flags()` | `src/rust/jsg/lib.rs` | | `realm_create()` FFI | `src/rust/jsg/lib.rs` (CXX bridge) | | C++ call site | `src/workerd/io/worker.c++` (`initIsolate`) | | Cap'n Proto schema | `src/workerd/io/compatibility-date.capnp` | | Generated Rust bindings | `//src/workerd/io:compatibility-date_capnp_rust` (Bazel target) | ## Constructors To allow JavaScript to create instances of a resource via `new MyResource(args)`, mark a static method with `#[jsg_constructor]`: ```rust use jsg_macros::{jsg_resource, jsg_method, jsg_constructor}; #[jsg_resource] struct Greeting { message: String, } #[jsg_resource] impl Greeting { #[jsg_constructor] fn constructor(message: String) -> Self { Self { message } } #[jsg_method] fn get_message(&self) -> String { self.message.clone() } } // JS: let g = new Greeting("hello"); g.getMessage() === "hello" ``` **Rules:** - The method must be static (no `self` receiver) and must return `Self`. - Only one `#[jsg_constructor]` is allowed per impl block. - The first parameter may be `&mut Lock` (or `&mut jsg::Lock`) if the constructor needs isolate access; it is not exposed as a JS argument. - If no `#[jsg_constructor]` is present, `new MyResource()` throws an `Illegal constructor` error, matching C++ JSG behavior. ## Properties Two macros expose accessor properties on resource types: `#[jsg_property]` for prototype and instance properties, and `#[jsg_inspect_property]` for debug-only symbol-keyed properties. Full reference documentation (arguments, naming rules, compat-flag behavior, examples) lives in **[`jsg-macros/README.md`](../jsg-macros/README.md#jsg_propertyrequired-placement--name---readonly)**. The summary below covers the most common patterns: ```rust use std::cell::{Cell, RefCell}; use jsg_macros::{jsg_resource, jsg_property, jsg_inspect_property}; #[jsg_resource] impl Counter { // Prototype property (on the proto chain; use this in almost all cases) #[jsg_property(prototype)] pub fn get_value(&self) -> jsg::Number { /* ... */ } #[jsg_property(prototype)] pub fn set_value(&self, v: jsg::Number) { /* ... */ } // Read-only prototype property (no paired `set_value`) #[jsg_property(prototype, readonly)] pub fn get_label(&self) -> String { /* ... */ } // Own-property accessor (use sparingly; inhibits minor-GC) #[jsg_property(instance)] pub fn get_id(&self) -> String { /* ... */ } // Inspect-only: shown by util.inspect() / console.log, invisible to JS #[jsg_inspect_property] pub fn debug_info(&self) -> String { /* ... */ } } ``` Methods **must** start with `get_` (getter) or `set_` (setter). The prefix is stripped for the JS name. Omitting the setter (or adding `readonly`) makes the property read-only. ## Static Constants To expose numeric constants on a resource class (equivalent to `JSG_STATIC_CONSTANT` in C++), use `#[jsg_static_constant]` on `const` items inside a `#[jsg_resource]` impl block: ```rust use jsg_macros::jsg_static_constant; #[jsg_resource] impl WebSocket { #[jsg_static_constant] pub const CONNECTING: i32 = 0; #[jsg_static_constant] pub const OPEN: i32 = 1; } // JS: WebSocket.CONNECTING === 0, instance.OPEN === 1 ``` Constants are set on both the constructor and prototype as read-only, non-configurable properties per Web IDL. The name is used as-is (no camelCase conversion). Only numeric types are supported (`i8`..`i64`, `u8`..`u64`, `f32`, `f64`). ## FFI Functions with Raw Pointers Functions exposed to C++ via FFI that receive raw pointers must be marked `unsafe fn`: ```rust pub unsafe fn realm_create(isolate: *mut v8::ffi::Isolate, feature_flags_data: &[u8]) -> Box { // ... } ```