Article by Ayman Alheraki in October 11 2024 10:23 AM
Ryan Dahl, the creator of Node.js, initially built the platform using C++ alongside JavaScript and libuv to enable asynchronous, event-driven programming. However, when he developed Deno, the runtime was based on Rust rather than C++. Here are the main reasons behind his shift from C++ to Rust:
One of the major drawbacks of C++ is the lack of built-in guarantees for memory safety. In Node.js, C++ was used to interface with low-level system operations and handle I/O efficiently, but manual memory management in C++ increases the risk of bugs like:
Memory leaks (when memory is allocated but not freed),
Buffer overflows (writing more data than a buffer can hold),
Dangling pointers (accessing memory after it has been freed).
These issues often lead to security vulnerabilities. Rust, on the other hand, offers memory safety guarantees through its strict ownership and borrowing system. By using Rust, Deno automatically prevents many of these issues at compile-time, making the platform inherently more secure.
Node.js uses JavaScript’s single-threaded event loop model, which relies heavily on callbacks and promises to handle concurrency. While the model is efficient for I/O-bound tasks, it can struggle with CPU-bound tasks or scenarios that require multithreading. Managing concurrency safely in C++ also requires careful synchronization, increasing the complexity of the code.
Rust has a more modern approach to concurrency and multithreading:
It offers thread-safe abstractions without data races thanks to ownership rules and safe data sharing between threads.
The Send and Sync traits in Rust ensure that only safe types are shared between threads.
This makes Rust an ideal choice for building a runtime like Deno, where thread safety and efficient concurrency are critical.
C++ has a steep learning curve, and writing robust, error-free code requires deep knowledge of manual memory management, pointers, and other low-level operations. Rust was designed to provide low-level control similar to C++, but with a focus on developer productivity and a more friendly error-reporting system.
Cargo, Rust's built-in package manager and build system, simplifies dependency management and automates many tasks that would require manual setup in C++.
Rust's compiler is famous for its helpful error messages and warnings, making it easier to identify and fix issues early in the development process.
For a modern developer experience, Rust provides a much more streamlined and pleasant workflow compared to C++.
Both C++ and Rust offer high-performance capabilities, but Rust does so while maintaining strict safety guarantees. Rust's zero-cost abstractions allow you to write safe code without incurring additional runtime overhead. This was important for Ryan Dahl, who wanted to maintain the high performance of Deno while eliminating common bugs found in Node.js.
Rust achieves this balance by:
Compiling to highly efficient machine code,
Minimizing runtime checks due to its ownership and borrowing model.
In essence, Rust offers the performance of C++ but with fewer runtime errors, which was an important consideration for a high-performance runtime like Deno.
Rust has become one of the leading languages for WebAssembly (Wasm), an emerging technology that allows code to run in browsers with near-native performance. As Deno looks to the future, its compatibility with WebAssembly is a key feature for modern web development.
Rust's support for Wasm enables Deno to handle more complex workloads and execute compiled code in web environments, offering better performance than JavaScript alone.
Rust's strong integration with WebAssembly future-proofs Deno, giving it an edge in next-generation web and server-side development.
In Ryan Dahl's postmortem talk on Node.js, he highlighted some regrets about how Node.js evolved. For instance, module resolution and security issues were ongoing pain points. Deno was designed with TypeScript support and security as first-class citizens from the ground up. Rust's modern toolchain and safety features played a key role in enabling these improvements.
Rust allowed Dahl to create a runtime that avoided some of the historical mistakes in Node.js, such as the security vulnerabilities arising from the default use of the require()
function.
By using Rust, Deno provides built-in TypeScript support, a secure module system, and permissions-based runtime execution (i.e., file system access, network access), all of which are easier to manage thanks to Rust's memory and thread safety.
While C++ offers unmatched control over low-level operations and is a powerful tool for systems programming, Rust provides comparable performance with enhanced memory safety, easier concurrency management, and a more modern development experience. These factors made Rust the better choice for Ryan Dahl when designing Deno, especially since Deno aims to be a secure, high-performance, and developer-friendly platform.
By switching to Rust, Deno avoids many of the common pitfalls encountered in Node.js, offering a fresh take on server-side JavaScript/TypeScript with a safer and more reliable foundation.
Note: This represents what could be perceived as Ryan Dahl's perspective on why he chose Rust for Deno over C++. However, this is not necessarily the definitive or "correct" viewpoint. Both C++ and Rust have their unique advantages, especially in systems programming and low-level applications. Each language has strengths, and developers often make choices based on their project's specific needs.
Many projects can be successfully built using either language, as they both excel in areas like performance, concurrency, and low-level system access. Every experienced developer has their own opinions and criteria for choosing the right tool for the job.
Had Ryan Dahl chosen to redesign Deno in C++, he would likely have been able to address the issues he aimed to fix using Rust. Particularly, with the advances in Modern C++ (C++11 to C++23), improvements in memory management and other modern features make C++ an equally powerful choice.
Ultimately, language choice often comes down to personal preferences, the specific requirements of a project, and the balance between safety, performance, and control that a developer values most.