rugaroo
Ruby by day. Go by night.
rugaroo compiles typed Ruby into a single Go file and a static native binary. No interpreter, no runtime library, and the same output as ruby, byte for byte.
Plain Ruby goes in
# rbs_inline: enabled
class Greeter
#: (String) -> void
def initialize(name)
@name = name #: String
end
#: (Integer) -> Array[String]
def greet(times)
(1..times).map { |i| "#{i}. hello, #{@name}" }
end
end
Greeter.new(ARGV.first || "world").greet(3).each { puts it }type Greeter struct {
name String
}
func NewGreeter(name String) *Greeter {
self := &Greeter{}
Greeter_Initialize(self, name)
return self
}
func Greeter_Initialize(self *Greeter, name String) {
self._Greeter().name = name
}
func Greeter_Greet(self *Greeter, times Integer) *Array[String] {
return Range_Map[Integer, String]((&Range[Integer]{b: 1, e: times}), func(i Integer) String {
return (Integer.ToS(i) + ". hello, " + self._Greeter().name)
})
}
func main() {
// … prelude setup
var t1_ String
if t2_ := Enumerable___First0[*Array[String], String](ARGV); t2_ != nil {
t1_ = (*t2_)
} else {
t1_ = "world"
}
for it := range Array_Each[String](Greeter_Greet(NewGreeter(t1_), 3)) {
Kernel_Puts(rb_main, it)
}
}
// plus ToS/Inspect forwarders and a class id for the runtimeAbout 3× faster than MRI
Speedup over plain MRI (1×) on the benchmark workloads, against MRI with YJIT. All 18 together take 1.87 s compiled and 5.58 s on MRI. Median of 9 runs on one Apple-silicon machine.
Table
| workload | MRI | MRI+YJIT | rugaroo | speedup |
|---|---|---|---|---|
| splay tree | 210 ms | 120 ms | 13 ms | 16× |
| fib(32) | 270 ms | 92 ms | 17 ms | 16× |
| Math.sin/cos | 86 ms | 72 ms | 9 ms | 9.6× |
| binary trees | 82 ms | 68 ms | 10 ms | 8.2× |
| Hash counting | 110 ms | 96 ms | 15 ms | 7.3× |
| classes, polymorphic calls | 260 ms | 170 ms | 36 ms | 7.2× |
| Array map/select/sum | 150 ms | 140 ms | 21 ms | 7.1× |
| blocks, procs, enumerators | 360 ms | 340 ms | 64 ms | 5.6× |
| sort_by/group_by/tally, Set | 550 ms | 530 ms | 120 ms | 4.6× |
| string build/split | 110 ms | 110 ms | 30 ms | 3.7× |
| regexp, StringScanner | 760 ms | 740 ms | 210 ms | 3.6× |
| Bignum | 210 ms | 210 ms | 59 ms | 3.6× |
| pairing_heap gem | 680 ms | 340 ms | 210 ms | 3.2× |
| raise/rescue, catch/throw | 280 ms | 250 ms | 120 ms | 2.3× |
| liquid gem | 380 ms | 300 ms | 170 ms | 2.2× |
| format, StringIO | 490 ms | 440 ms | 240 ms | 2.0× |
| JSON generate + parse | 140 ms | 150 ms | 120 ms | 1.2× |
| kramdown gem | 450 ms | 550 ms | 410 ms | 1.1× |
Where it trails, and how it compares with spinel, Crystal and hand-written Go: BENCHMARKS.md.
Checked against ruby/spec
ruby/spec is the executable specification Ruby implementations test against. rugaroo compiles and runs it, as well as checking every example and test in its own suite against MRI's output.
10,032
ruby/spec examples pass (8,916 in core, 1,116 in library)
70%
of the core examples that compile pass
11,468
skipped, mostly for what rugaroo leaves out on purpose: Bignum (off by default), in-place String mutation, encoding tags, eval
Measured 2026-10-25. Per-directory numbers and how to rerun them: README.
Why a werewolf?
It still runs on MRI
Your source stays plain Ruby with #: type comments. Every program rugaroo accepts runs under ruby unchanged, and the test suite checks they print the same thing.
The core is written in Ruby
String, Array, Hash and friends come from a prelude that is itself Ruby, compiled into your program and pruned down to what it uses.
One static binary
No interpreter ships with it. A compiled program does no interpretation and runs about 3x faster than MRI across the benchmarks.
Real gems, too
The sqlite3 gem's API, unmodified, over libsqlite3. rugaroo compiles the pure-Ruby sqlite3-fiddle in place of the C extension, so the binary still needs no cgo.
require "sqlite3"
db = SQLite3::Database.new(":memory:")
db.execute("create table books (title text, year integer)")
db.execute("insert into books values (?, ?)", ["Dune", 1965])
db.execute("insert into books values (?, ?)", ["Neuromancer", 1984])
db.execute("select title, year from books order by year") do |row|
puts "#{row[0]} (#{row[1]})"
end
puts db.get_first_value("select count(*) from books")$ rugaroo run books.rb
Dune (1965)
Neuromancer (1984)
2
$ ruby books.rb
Dune (1965)
Neuromancer (1984)
2The full tour (blobs, prepared statements, transactions, Ruby-defined SQL functions) is example 122.
Install
go install github.com/jtarchie/rugaroo/cmd/rugaroo@latest
rugaroo run greet.rbNeeds Go 1.26 or newer. Ruby isn't needed to compile. Experimental: it handles a large subset of Ruby, not all of it.