A cute purple rougarou pup wearing a ruby pendant, in front of a full moon

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

greet.rb
# 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 }
typed Go comes out (your code; the prelude is left out)
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 runtime

About 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.

rugarooMRI + YJITMRI = 1×
splay tree16×
fib(32)16×
Math.sin/cos9.6×
binary trees8.2×
Hash counting7.3×
classes, polymorphic calls7.2×
Array map/select/sum7.1×
blocks, procs, enumerators5.6×
sort_by/group_by/tally, Set4.6×
string build/split3.7×
regexp, StringScanner3.6×
Bignum3.6×
pairing_heap gem3.2×
raise/rescue, catch/throw2.3×
liquid gem2.2×
format, StringIO2.0×
JSON generate + parse1.2×
kramdown gem1.1×
Table
workloadMRIMRI+YJITrugaroospeedup
splay tree210 ms120 ms13 ms16×
fib(32)270 ms92 ms17 ms16×
Math.sin/cos86 ms72 ms9 ms9.6×
binary trees82 ms68 ms10 ms8.2×
Hash counting110 ms96 ms15 ms7.3×
classes, polymorphic calls260 ms170 ms36 ms7.2×
Array map/select/sum150 ms140 ms21 ms7.1×
blocks, procs, enumerators360 ms340 ms64 ms5.6×
sort_by/group_by/tally, Set550 ms530 ms120 ms4.6×
string build/split110 ms110 ms30 ms3.7×
regexp, StringScanner760 ms740 ms210 ms3.6×
Bignum210 ms210 ms59 ms3.6×
pairing_heap gem680 ms340 ms210 ms3.2×
raise/rescue, catch/throw280 ms250 ms120 ms2.3×
liquid gem380 ms300 ms170 ms2.2×
format, StringIO490 ms440 ms240 ms2.0×
JSON generate + parse140 ms150 ms120 ms1.2×
kramdown gem450 ms550 ms410 ms1.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.

books.rb
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")
same output from ruby and from the binary
$ rugaroo run books.rb
Dune (1965)
Neuromancer (1984)
2

$ ruby books.rb
Dune (1965)
Neuromancer (1984)
2

The 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.rb

Needs Go 1.26 or newer. Ruby isn't needed to compile. Experimental: it handles a large subset of Ruby, not all of it.