Architecture and technologies that support Deno
Architecture-wise, Deno took various topics into consideration such as security. Deno put much thought into establishing a clean and performant way of communicating with the underlying OS without leaking details to the JavaScript side. To enable that, Deno uses message-passing to communicate from inside the V8 to the Deno backend. The backend is the component written in Rust that interacts with the event loop and thus with the OS.
Deno has been made possible by four pieces of technology:
- V8
- TypeScript
- Tokio (event loop)
- Rust
It is the connection of all those four parts that make it possible to provide developers with a great experience and development speed while keeping the code safe and sandboxed. If you are not familiar with these pieces of technology, I'll leave a short definition:
V8 is a JavaScript engine developed by Google. It is written in C++ and runs across all major operating systems. It is also the engine behind Chrome, Node.js, and others.
TypeScript is a superset of JavaScript developed by Microsoft that adds optional static typing to the language and transpiles it to JavaScript.
Tokio is an asynchronous runtime for Rust that provides utilities to write network applications of any scale.
Rust is a server-side language designed by Mozilla focused on performance and safety.
Using Rust, a fast-growing language, to write Deno's core made it more approachable for developers than Node.js. Node.js' core was written in C++, which is not known for being exceptionally easy to deal with. With many pitfalls and with a not-so-good developer experience, C++ revealed itself as a small obstacle in the evolution of Node.js core.
Deno_core
is shipped as a Rust crate (package). This connection with Rust is not a coincidence. Rust provides many features that facilitate this connection with JavaScript and adds capabilities to Deno itself. Asynchronous operations in Rust typically use Futures that map very well with JavaScript Promises. Rust is also an embeddable language, and that provides direct embedding capabilities to Deno. This added to Rust being one of the first languages to create a compiler for WebAssembly, made the Deno team choose it for its core.
Inspiration from POSIX systems
POSIX systems were of great inspiration to Deno. In one of his talks, Dahl even states that Deno handles some of its tasks "as an operating system".
The following table shows some of the standard terms from POSIX/Linux systems and how they map to Deno concepts:
Some of the concepts from the Linux world might be familiar to you. Let's take, for instance, processes. They represent an instance of a running program that might execute using one or multiple threads. Deno uses WebWorkers to do the same job inside the runtime.
In the second row, we have syscalls. If you aren't familiar with them, they are the way for programs to perform requests to the kernel. In Deno, these requests do not go directly to the kernel; instead, they go from the Rust core to the underlying operating system, but they work similarly. We'll have the opportunity to see this in the upcoming architecture diagram.
These are just a couple of examples you might recognize if you are familiar with Linux/POSIX systems.
We'll explain and use most of the aforementioned Deno concepts throughout the rest of this book.
Architecture
Deno's core was initially written in golang, but it later changed to Rust. This decision was made to get away from golang as it is a garbage-collected language. Its combination with V8's garbage collector could lead to problems in the future.
To understand how the underlying technologies interact with each other to form the Deno core, let's look at the following architecture diagram for it:
Deno uses message passing to communicate with the Rust backend. As a decision in regard to privilege isolation, Deno never exposes JavaScript object handles to Rust. All communication in and out of V8 uses Uint8Array
instances.
For the event loop, Deno uses Tokio, a Rust thread pool. Tokio is responsible for handling I/O work and calling back the Rust backend, making it possible to handle all operations asynchronously. Operations (ops) is the name given to the messages that are passed back and forth between Rust and the event loop.
All the asynchronous messages dispatched from Deno's code into its core (written in Rust) return Promises back to Deno. To be more precise, asynchronous operations in Rust usually return Futures, which Deno maps to JavaScript Promises. Whenever these Futures are resolved, the JavaScript Promises are also resolved.
To enable communication from V8 to the Rust backend, Deno uses rusty_v8
, a Rust crate created by the Deno team that provides V8 bindings to Rust.
Deno also includes the TypeScript compiler right inside V8. It uses V8 snapshots for startup time optimization. Snapshots are used for saving the JavaScript heap at a specific execution time and restoring it when needed.
Since it was first presented, Deno was subject to an iterative, evolutionary process. If you are curious about how much it changed, you can look at one of the initial roadmap documents written back in 2018 by Ryan Dahl (https://github.com/ry/deno/blob/a836c493f30323e7b40e988140ed2603f0e3d10f/Roadmap.md).
Now, not only do we know what Deno is, but we also know what's happening behind the scenes. This knowledge will help us in the future when we're running and debugging our applications. The creators of Deno made many technological and architectural decisions to bring Deno to the state it is today. These decisions pushed the runtime forward and made sure Deno excels in several situations, some of which we'll later explore. However, to make it work well for some use cases, some trade-offs had to be made. Those trade-offs resulted in the limitations we'll examine next.