Rust for JavaScript Developers: The 2026 Migration Guide
Rust is everywhere in the JS ecosystem now — bundlers, compilers, linters, and the runtimes you deploy to are increasingly written in it. You do not need to abandon JavaScript to benefit. But learning Rust is easier if you map it onto concepts you already understand.
The mental model shift
JavaScript is dynamic: types live at runtime, and you can mutate anything. Rust is the opposite — types are checked at compile time, and correctness is enforced before your program runs. Think of it as TypeScript with the training wheels off and memory safety baked in.
Ownership instead of garbage collection
The biggest jump. JavaScript has a garbage collector; Rust does not. Instead, every value has one owner, and ownership can be moved. When you don't need a value, it is dropped automatically.
let s = String::from("hello");
let t = s; // s is MOVED into t, s is now unusable
// println!("{}", s); // compile error: value borrowed/moved
It feels restrictive until you internalize it: "who owns this, and can I borrow it?" Most Rust errors during the learning phase are ownership errors, and the compiler tells you exactly what to do.
The amazing part: the compiler as mentor
The Rust compiler (rustc) is legendary for its error messages. It does not just say "error" — it explains the problem and suggests the fix. For a JavaScript developer, this turns the compiler into a pair programmer. The first week you'll fight it; by week two you'll trust it.
Borrowing: the currency of the language
fn print_len(s: &str) { // borrow, don't own
println!("{}", s.len());
}
&str is a borrow — read-only access without taking ownership. &mut is a mutable borrow — exclusive write access. The rules: either you can borrow immutably many times, or mutably once. This is what kills data races at compile time.
Structs and enums replace the JS toolkit
-
struct≈ a typed object/class shape. -
enum≈ an object that is exactly one of several variants (like a discriminated union in TypeScript, but exhaustive — the compiler checks every case). -
match≈switch, but exhaustive: the compiler forces you to handle every variant.
Error handling: Result instead of try/catch
JavaScript throws exceptions. Rust returns Result<T, E>. You process it with ? or match:
use std::fs;
fn read() -> Result<String, std::io::Error> {
fs::read_to_string("config.toml") // returns Result
}
The ? operator unwraps success or early-returns the error. It's explicit, and the type system tracks exactly what can fail.
When to actually use Rust
- Performance-critical hot paths in bundlers, parsers, or APIs.
- WebAssembly — compile one Rust module and run it in browser, edge, and server.
- CLI tools — small, fast, single-binary utilities.
- Safety-critical logic where a bug in a data race is unacceptable.
For the rest — UI, glue, quick prototypes — JavaScript/TypeScript is still the right tool. You are not choosing; you are adding a tool.
How to start
- Install via rustup. Run
cargo new hello. - Pick a small task you already know in JS (parse some data, write a CLI) and port it.
- Read the ownership chapter of the Rust Book when you hit borrow errors.
- Let the compiler mentor you — read every error message it prints.
You'll find the syntax familiar and the guarantees liberating. The friction is real, but it is upfront — and it buys you correctness and speed that JavaScript's runtime-checking simply cannot offer.
Top comments (0)