Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Why Is It Designed This Way?

Design reasoningBoundary placementRevision 89f33cb

Why not let JavaScript call Rust objects directly?

V8 values obey garbage collection and isolate rules; Rust values obey ownership and lifetimes. An op is a narrow conversion boundary between those systems. Its generated glue validates JavaScript inputs, invokes typed Rust, and maps a typed result or error back into V8.

Why keep the isolate single-owner?

V8 execution and most isolate state are not ordinary shared concurrent data. Keeping one owner avoids putting locks around the JavaScript heap and makes a single event-loop turn coherent. Parallelism is introduced by separate workers and by native work outside the isolate, not by concurrently mutating one heap.

Why have an event loop if Tokio already schedules futures?

Tokio answers when Rust futures can make progress. Deno must additionally decide when to resolve JavaScript promises, run microtasks, advance modules, fire timers, report rejections, and determine whether the JavaScript program is still alive. Those are language-runtime policies, not generic executor policy.

Why use resource IDs?

A TCP stream may outlive the connect op that created it. JavaScript needs a stable handle, while Rust must keep owning the concrete stream. ResourceTable stores Rc<dyn Resource> under a small integer. Each later op asks for the expected concrete type; a missing or mismatched ID becomes an error.

This is intentional type erasure at one collection boundary:

JavaScript: rid 7
               ↓ lookup as TcpStreamResource
Rust table: Rc<dyn Resource> → Rc<TcpStreamResource>

Why check permissions inside ops?

The JavaScript wrapper is not a security boundary. Native code is the last point before a filesystem or network side effect. Checking there keeps permission enforcement adjacent to the capability and protects alternate internal callers of the same op path.

Why both Rc<RefCell<_>> and thread-safe types?

Not all concurrency is shared-memory parallelism. Isolate-local OpState can be Rc<RefCell<_>> because it is accessed on one local execution context. Network drivers, worker-control paths, and genuinely cross-thread services use the appropriate Send, atomics, locks, or channels. Choosing synchronization from actual ownership is better than making every type thread-safe by default.

Why does cancellation use a resource?

An AbortSignal exists in JavaScript while the pending operation exists in Rust. A temporary CancelHandle in the resource table bridges those lifetimes. Closing it can wake/cancel the Rust future; every exit path removes it so the bridge does not leak.

The broader lesson is that Deno is not “JavaScript running on Tokio.” It is a language runtime that uses Tokio beneath carefully controlled semantic boundaries.