roundhouse picks the target language at build time, not at runtime
Rails as a specification; the deployment target is a build flag
At a glance
- What is it?
- roundhouse reads Rails source and emits a standalone project in one of about ten target languages, with the target chosen as a compiler flag. Its strongest claim is not speed but verification: every emitter must return byte-comparable responses to live Rails on every push, and the one target that is not really a translation is the one it says is closest to Rails.
- Who is it for?
- roundhouse fits a team that owns a Rails application, wants to keep the source, and has decided the runtime is the problem rather than the code, since the analysis alone is a static type checker you can put in CI today. It does not fit a team that wants to keep Ruby at runtime and just speed it up, and the Spinel path exists precisely so that case is covered without leaving Ruby.
- Can I use it commercially?
- Yes. Apache-2.0 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 18 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 4, 2026, and from our analysis. They are not legal advice.
Star history
- rubys/roundhouse381 ★ · +294
| Repository | 2026-08-21 | 2026-10-04 |
|---|---|---|
| rubys/roundhouse | 87 | 381 |
294 Stars in the last 44 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The one option nobody occupies is static, deep, and annotation-free
The analysis half of this project is the part you can use without committing to a rewrite, and the competitive framing explains why it exists. The project argues that Rails was already typed: an association declaration is a type declaration, and the framework's conventions carry type information nobody wrote down. Roundhouse recovers it by whole-program inference from unmodified source, working out which class an association returns, what columns a model has and what they deserialize to, and whether a lookup came back nil, all without booting the application or touching a database. The gap it names is precise. The Ruby language server is static but stops at names. The Rails-aware tooling is deep but needs a running application. The static type checkers for Ruby are both static and deep, and you pay for them in annotations, though if your repository already has them the inference reads your existing signatures as seeds. That is a specific hole rather than a general complaint, and the claim underneath it is a number: a pass over Mastodon, 1,173 files including all 337 controllers and the HAML views, takes about 1.5 seconds.
The correctness story is a differential compare, not a unit test suite
Transpilers are easy to write and hard to trust, so the verification method is the part to read. The bar is set as an oracle: the same URL fetched from Rails and from each emitted target has to produce the same response. That is checked three ways, in increasing order of what they can see. Emitted tests come with the generated project. Then a differential compare runs against live Rails, matching DOM node for DOM node and JSON value for value, which catches the structural drift a string comparison would miss. Then browser end-to-end tests cover what a static diff cannot reach at all, since behaviour that only appears after script execution is invisible to a tree diff. The claim is that every target on the list passes all three on every push, which converts the question from whether the output looks right into whether it behaves right against the original. The published artefact for checking that claim is not a table but a page showing what every emitter produces from one fixture, updated on each push, so a regression is visible in the repository rather than in a changelog.
The most faithful target is the one that is not a translation
Among the roughly ten targets, one behaves closest to Rails, and the reason given is the interesting part. That target compiles the Ruby shape ahead of time into a single native binary using Spinel, an ahead-of-time Ruby compiler. It stays closer to the original by a distance, and the stated reason is that it runs the framework runtime itself rather than a translation of it. Everything else about it follows from that: one executable, no interpreter, and a SQLite file beside the binary by default, with the database treated as a build flag exactly like the target language. The honest reading is that the emitters are where fidelity risk lives, and this one is not an emitter so much as a compiler sitting behind the same interface. A real product runs this way, with every page and every websocket frame compared against live Rails on every push, and the project publishes an archive image you can run in minutes to see it rather than take the word for it.
The benchmark table carries its own caveat in the paragraph underneath
The performance section makes a specific argument and then narrows it honestly. The premise is that Rails is, operationally, an interpreter for your application: routes, associations, validations and templates are data the framework consults on every request, and every decision whose answer cannot differ between requests can be made once at transpile time, leaving only the per-request residue. The July 2026 round on one fixture, serving the HTML index on a fixed Linux server, gives four numbers. Rails on CRuby with YJIT handles 326 requests per second, Rails on JRuby 1,066, the emitted output on CRuby with YJIT 3,292, and the emitted output on JRuby 24,172. The decomposition is that stripping the interpretive layers is worth about ten times on the same interpreter, and that the static, monomorphic Ruby left over is the shape a JVM just-in-time compiler was built for, worth a further seven times. Then the caveat: these are ratios from a CPU-bound microbenchmark of a small fixture, and the live numbers with their environment capture live on a separate bench page. A benchmark that names its own limits is more useful than one that does not.
The language server dependency is synchronous on purpose, and that is why there is a wasm build
A small detail in the dependency list explains an architectural decision. The language server crate is described in the manifest as rust-analyzer's synchronous transport, and the comment states there is deliberately no async runtime, because that keeps the analysis engine synchronous. On top of that, the crate is marked host-only, and the comment notes the wasm build excludes it. That exclusion is why an in-browser editor exists at all: the analyser is compiled to WebAssembly for a page that analyses a folder on your disk without uploading it, and the synchronous engine is the shape that compiles cleanly to a browser target while the server-side language server does not need to travel. The manifest also carries a comment about what does not ship, namely that one multi-call binary goes on the path while the alias binaries and the internal-representation development tools stay cargo-only. So the surface area is deliberately narrow: one command for users, everything else for people working on the compiler.
It is not on crates.io, so the install is a script or a git install
The distribution is unusual enough to trip people up. The package sets publishing to false, with a comment that it is not on the registry but its binary is released, and that the release tooling needs the opt-in in order to see the package at all. That means you cannot add it as a dependency, and the two documented install paths are a piped installer script and a build from the git repository:
cargo install --git https://github.com/rubys/roundhouse --bin roundhouseThe release builds are named by date rather than by semver, and the README is candid about coverage: binaries for macOS on Apple silicon and Linux on x86-64, plus macOS on Intel and Windows builds it calls untested. A separate release document says what each snapshot contains, which is the thing you want when a dated build might have shipped a regression. The Rust floor is stated in the manifest as 1.85, on edition 2024, and the manifest is dual licensed, so the permissive option is available whichever of the two licence files you would rather cite. A builds document and an agent guide sit at the top level next to the rationale document the README points at for the case for doing any of this.
The name is a rail yard, and it collides with a boxing term and a music venue
The project explains its own metaphor, which is the best one available. A roundhouse is the circular hub in a rail yard where engines rotate and route onto different tracks, and that is exactly the shape here: one Ruby source at the centre, analysed and dispatched to one of several target tracks. The metaphor is doing real work, because it frames the target as a routing decision rather than a rewrite. It also explains why the database is a build flag, since a route to a different track carries its own carriage. What the name costs is legibility. Roundhouse is a Muay Thai technique, a London music venue, a town landmark and a clothing brand, and anyone searching for this tool by name will not find it. The project's own solution is to point at its published site, its in-browser analyser, the compiled product archive and the emitter comparison rather than at a search result, and for anyone evaluating it, that is the practical entry point anyway: the analyser needs no install and no key.
Editorial conclusion
roundhouse fits a team that owns a Rails application, wants to keep the source, and has decided the runtime is the problem rather than the code, since the analysis alone is a static type checker you can put in CI today. It does not fit a team that wants to keep Ruby at runtime and just speed it up, and the Spinel path exists precisely so that case is covered without leaving Ruby. Before you transpile anything, run `roundhouse check` on your application first: the inference that feeds every emitter is also a linter, and a repository that trips the nil-safety findings is telling you the transpile will be harder than the demo suggests.
Frequently asked questions
How do I install roundhouse?
Either with the piped installer script, or from source with `cargo install --git https://github.com/rubys/roundhouse --bin roundhouse`. The package sets publishing to false, so it is not on the crates.io registry even though its binary is released. Release binaries cover macOS on Apple silicon and Linux on x86-64, with macOS-Intel and Windows builds described as untested.
What target languages does roundhouse emit?
Roughly ten: standalone projects in Rust, Go, TypeScript, Crystal, Elixir, Kotlin, Swift, Python, C# and Ruby, each with its own tests and no Rails at runtime. The database is also a build flag, so a compiled target ships a SQLite file beside the binary by default.
How does roundhouse know the emitted code is correct?
With a conformance oracle: the same URL fetched from Rails and from each target has to produce the same response. That is checked by emitted tests, a differential compare against live Rails matching DOM node for DOM node and JSON value for value, and browser end-to-end tests for what a static diff cannot reach. Every target passes all three on every push.
Does roundhouse need type annotations on a Rails app?
No. The argument is that Rails was already typed, since an association declaration is a type declaration, and the inference recovers what class an association returns, what columns a model has and what they deserialize to, from unmodified source without booting the app. If your repository already carries static type signatures, the inference reads them as seeds.
Can I use roundhouse only as a type checker?
Yes. The analysis is a product in its own right, available as a terminal or CI command, an editor language server, an agent-facing server, and an in-browser editor that analyses a folder without uploading it. A pass over Mastodon, 1,173 files including all 337 controllers and the HAML views, is reported at about 1.5 seconds.
Official sources
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.
[](https://hysenlabs.com/projects/rubys-roundhouse)Star history chart for your README
[](https://hysenlabs.com/projects/rubys-roundhouse)