A tiny Ruby-subset interpreter written in Rust, built on Prism. Small, memory-safe, embeddable — and it compiles to WebAssembly. The mruby niche, in Rust.
faster end-to-end than CRuby 3.4 on DSL hosting — the actual product-niche benchmark.
Parse, load, run, exit. That's the real product-niche workload — and where the gap is widest.
| Shape | Time |
|---|---|
| momiji (native) | 1.5 ms |
| momiji · cwasm (AOT + wizer) | ~7 ms |
| momiji · wasm (raw JIT) | 12.7 ms |
| CRuby 3.4 | 78 ms |
| momiji | CRuby | |
|---|---|---|
| Time | 0.33 s | 0.19 s |
| Peak memory | 2.1 MB | 18.4 MB |
Per-run resource caps; trip one and you get a clean ResourceExhausted trap — never a host panic.
The shape is Lua-in-Rust + Ruby grammar + sandbox — not CRuby with fewer features. Embedders building sandboxed DSL hosts get deterministic-by-default behaviour.
let mut rt = Runtime::with_config(Config { fuel: Some(1_000_000), max_heap_objects: Some(10_000), max_frames: Some(128), ..Default::default() }); rt.register_fn("host_pid", |_| { Ok(Value::Int(std::process::id() as i64)) }); rt.eval(r#"puts "pid #{host_pid}""#, "inline")?;
Ship the raw .wasm, or precompile to .cwasm with wizer pre-initialization.
Native binary — the baseline.
cwasm — AOT + wizer snapshot. The shipping shape.
Raw .wasm, JIT on load — still 6× faster than CRuby.
Rust front (hyper + tokio), Ruby handler. Add _fiber for true SSE streaming.
class SSEStream def each 10.times { |i| yield "data: tick #{i}\n\n" } end end app = ->(env) { [200, {"Content-Type" => "text/event-stream"}, SSEStream.new] } __rubyrs_http_serve_with_app("127.0.0.1:9292", 60, app)
Each yield becomes one HTTP/1.1 chunked frame, flushed before the next is produced — SSE, long-poll, large files.
Not a Ruby gem host. The omissions are deliberate. Need Rails, Sinatra, Bundler, or gems? Use CRuby.
*splat, **kwargsrequire for user .rb files & $LOAD_PATHpathname · set · stringio · strscanevalKernel#load · real autoload$LOAD_PATHJSON.parse, YAML.load, URI.parse — stubbedA sibling crate and the first real driver of the embedding API. Gemfiles are Ruby DSLs, so it doubles as in-tree dogfooding.
# Gemfile is just a Ruby DSL source "https://rubygems.org" gem "rails", "~> 7.1" group :test, :development do gem "rspec" gem "rubocop", require: false end # → parsed end-to-end in ~0.4 ms
Momiji's siblings in the momiji-rs org — each one matched byte-for-byte against the tool it replaces.
A from-scratch dart-sass alternative. Zero dependencies, wasm-friendly, library and CLI.
Parses the whole CommonMark 0.31.2 grammar, aiming for byte-identical output with cmark.
Byte-identical HTML with Ruby kramdown for a growing subset — anything outside it is a clean decline, never a guess.