Open-source project
maekawatoshiki/rapidus avatar
maekawatoshiki/rapidus

Rapidus is a JavaScript engine in Rust that is aiming at ES5

ECMAScript implementation in Rust

594 stars20 forksRustMIT

At a glance

What is it?
Rapidus is a small ECMAScript engine written in Rust, built on ideas borrowed from Boa, with a partial Tracing-JIT, a multiline aware REPL and an experimental path for loading Rust as a native module. It is a research scale engine rather than a browser engine, and its own README says it targets ES5.
Who is it for?
Rapidus suits a developer who wants to read a JavaScript engine end to end, or who is porting a JavaScript runtime idea to Rust, because the codebase is split into a lexer, a parser, an AST, a core and a REPL, and the author credits Boa as the source of the design ideas.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 112 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

An engine split into lexer, parser, AST, core and REPL

The repository layout is the architecture, and it is a textbook one. The top level listing shows ast/, core/, lexer/, parser/ and repl/ as separate directories, and the Cargo.toml confirms them as the five members of a Cargo workspace. A lexer that turns source text into tokens, a parser that turns tokens into a tree, an AST crate that defines the tree, a core that holds the runtime, and a REPL that gives you a prompt. There is no separate crate for a garbage collector or a compiler, so the runtime work lives in core. The description is honest about scale and goal in one line, a JavaScript engine implemented in Rust, now aiming at ES5. ES5 is the key word. It means the language target is the 2009 edition, so there is no promise of classes as syntax, no arrow functions, no async and await and no modules in the language sense, which puts the engine in a different category from anything you would ship. The author credits Boa, the Rust JavaScript engine, with big thanks, and the project is MIT licensed with a Rust build workflow badge and a Codecov badge on the main branch.

What the feature list actually claims

The Features section has three entries and each is a statement about scope rather than a capability list. Small, which is the design goal rather than a measurement. Partly support for Tracing-JIT compiling, where the word partly is doing the work. And a REPL. That is the whole feature list, and it is worth reading alongside the ES5 target, because together they say this is an engine for understanding how a JavaScript engine works rather than an engine for running modern JavaScript at scale. The Tracing-JIT point deserves a sentence of its own. A tracing JIT records the hot paths a program actually takes and compiles those, as opposed to compiling everything up front, and the phrase partly support means the machinery is there and incomplete, which for an engine in this state is the expected state. The REPL is the part that gives the engine its user interface, and the README spends more space on it than on anything else, including a transcript of a multiline function definition, a factorial evaluated to 3628800, and a full trace of bytecode execution with the program counter, the instruction and the execution time per instruction in microseconds.

Building it: Nightly Rust and an LLVM 6.0 that has to be symlinked

The build instructions are seven steps and two of them exist only because of a toolchain mismatch that the author could not avoid. You install Rust with the rustup script, then set the toolchain to nightly.

sh
curl https://sh.rustup.rs -sSf | sh
sh
rustup override set nightly

Then you install the dependencies, listed as LLVM 6.0 plus other packages as necessary, with the example command for Ubuntu or Debian.

sh
# e.g. Ubuntu or Debian
apt-get install llvm-6.0

The tests then run with cargo test, and the README warns that if the compilation failed because of LLVM-related errors, a symlink may help.

sh
ln -sf /usr/bin/llvm-config-6.0 /usr/bin/llvm-config

Building and running follow, with cargo run --release for the build and cargo run --release examples/XXX.js to run a script.

sh
cargo run --release
sh
cargo run --release examples/XXX.js

The LLVM 6.0 requirement is the sharpest constraint here. That version is old, and distributions do not all ship it in the default repositories, which is why the symlink advice appears at all. The nightly requirement is a second constraint, since it means you are building against a moving compiler rather than a stable one. Both are acceptable for a project you want to read, and both are reasons not to expect a one command install on a modern workstation.

The REPL knows when a function definition has ended

The REPL is the piece the author spent the most time on, and the transcript shows why. It is multiline aware, so a function body typed across several lines is recognised as a block rather than as a syntax error, and the transcript marks each point where the end of the input is recognised.

sh
$ cargo run
> function fact(n) {
... if (n < 2) {     <- recognize multilined input
... return n
... } else {
... return n * fact(n-1)
... }
... }                <- recognize the end of input
undefined
> fact(10)
3628800

The function definition evaluates to undefined, which is correct JavaScript behaviour for a declaration, and the call returns 3628800. The debug mode is the other half, and it is where the engine shows its work. Passing the --trace option prints each bytecode instruction as it executes, with the program counter, the instruction name, the value at the top of the execution stack and the time per instruction.

sh
$ cargo run -- --trace
> function fibo(x) { if (x<2) return 1; return fibo(x-1)+fibo(x-2)}
00020m 00000 Return                    <empty>
undefined
> fibo(3)
00009m 00000 PushInt8 3                <empty>

The trace output is annotated with the stack, the program counter and the timing diagram, which makes it a teaching tool as much as a debugger. For a reader trying to understand how a bytecode interpreter dispatches, this is more useful than any amount of prose, and it is the strongest argument for the project existing.

Loading Rust as a native module, marked experimental

The most ambitious feature is the ability to write a native module in Rust and load it into the engine, and the README puts THIS FEATURE IS EXPERIMENTAL in capitals immediately above it. The procedure is a seven step tour of the cargo and linker workflow. You create a library project next to the engine directory, so cargo new hello --lib produces a hello folder alongside the rapidus folder. You then add the engine as a path dependency in Cargo.toml and mark the library as a cdylib.

toml
# Add the followings to Cargo.toml

[dependencies]
rapidus = { path = "../rapidus" }
# other dependencies if you want...

[lib]
name = "hello"
crate_type = ["cdylib"] # try 'dylib' if it doesn't work.

The Rust source defines an initialize function that the engine calls to produce the module's exports, using a make_object! macro, and a second function that becomes a callable export.

rust
#[no_mangle]
fn initialize(vm: &mut VM, _: &Vec<Value>, _: CallObjectRef) -> Result<(), RuntimeError> {
    // make_object!() is useful
    let module_exports = make_object!(
        greet:   Value::default_builtin_function(greet),
        message: Value::String("hello".to_string())
    );

    vm.set_return_value(module_exports); // We have to return module.exports

    Ok(())
}

After building, the shared library is copied into the engine's directory and required from the REPL like any other module.

sh
$ cargo run
> var mod = require('hello')
> mod.greet()
Hello World from Rust DLL!
> mod.message
'hello'

The crate_type line carries a fallback comment, try dylib if it doesn't work, and that is a fair signal of the feature's maturity. It is also the most interesting design idea in the project, since it makes the engine a host for native code rather than an isolated interpreter.

What this is not, and how it compares to Boa

Two comparisons settle what this project is for. The first is with Boa, which the author credits openly, and Boa is the reference point for what a serious Rust JavaScript engine looks like, with a much larger language target and a much larger surface of tested behaviour. Rapidus is smaller and older in scope, and the useful way to read it is as a compact implementation of the same ideas rather than a Boa alternative. The second comparison is with the two mainstream approaches to running JavaScript outside a browser. A full engine such as V8 is enormous and is not something you read to learn how interpreters work, while a tree-walking interpreter built for embedding in a host application is usually small and offers no JIT at all. Rapidus sits between those, with a bytecode layer, a partial tracing JIT and a REPL, which makes it a good subject for study and a poor dependency for production. The platform answer is the most direct statement of scope in the README. The section on building on other platforms says I don't know. The macOS note has been struck through and now says it works, and the original advice was that you cannot use llvm installed with brew and should use macports or docker instead, so the practical situation is that Linux is the supported path and everything else is best effort. The repository is not archived, the last push was on 2026-07-04, and there are no GitHub releases, so what you get is a branch you read rather than a version you depend on.

Editorial conclusion

Rapidus suits a developer who wants to read a JavaScript engine end to end, or who is porting a JavaScript runtime idea to Rust, because the codebase is split into a lexer, a parser, an AST, a core and a REPL, and the author credits Boa as the source of the design ideas. It is the wrong tool for production script execution, since the target is ES5, the JIT support is described as partial, and the README says plainly that it does not know how to build on platforms other than Linux. It is also the wrong tool if you want a maintained engine, since the repository has no GitHub releases and the last push was on 2026-07-04. Before you build it, read the build steps closely, because it needs Rust Nightly and LLVM 6.0, and try the REPL on a factorial before you judge the implementation.

Frequently asked questions

What is Rapidus and what language level does it target?

Rapidus is a JavaScript engine implemented in Rust, and the README says it is now aiming at ES5. The project is built on ideas borrowed from Boa, is MIT licensed, and is split into ast, core, lexer, parser and repl crates in a Cargo workspace.

What does it take to build Rapidus from source?

Rust via rustup, then rustup override set nightly, then LLVM 6.0 as a dependency, with a symlink from llvm-config-6.0 to llvm-config if the compilation fails with LLVM errors. You run cargo test, then cargo run --release, and cargo run --release examples/XXX.js to run a script.

Can Rapidus load native modules written in Rust?

Yes, but the README marks this feature as experimental. You create a cargo library with a cdylib crate type next to the engine directory, depend on rapidus by path, define an initialize function that returns the module exports, copy the built shared library into the engine's directory, and require it from the REPL.

Official sources

  1. Issues
  2. License: MIT
  3. maekawatoshiki/rapidus on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/maekawatoshiki-rapidus.svg)](https://hysenlabs.com/projects/maekawatoshiki-rapidus)

Was this useful?