Help us shape the future of Go! Take the 2026 Go Developer Survey

rugaroo

package module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: MIT Imports: 12 Imported by: 0

README

rugaroo

Ruby by day. Go by night.
Compile typed Ruby to a native Go binary.
rugaroo.dev · playground

A rougarou is a shape-shifter from Cajun folklore. This one turns Ruby into Go.

rugaroo reads a Ruby program whose methods carry rbs-inline type annotations (#: (String) -> Integer comments), translates it to a single Go file, and builds it with the Go toolchain. The result is a static executable with no Ruby interpreter and no runtime library: Ruby's core classes are compiled in from a prelude that is itself written in Ruby.

The source stays plain Ruby. Every program rugaroo accepts also runs on MRI (the standard ruby) unchanged, and the test suite holds rugaroo to that: for each example, the compiled binary's stdout and exit code must match ruby main.rb byte for byte.

Status: the core language, core classes and most of the standard library are stable: tested against MRI, and a difference is a bug. Gems, fiddle and a few newer features are experimental. It is not a drop-in replacement for ruby; docs/support.md says what is in which tier.

Example

# 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 }
counts = %w[a b a c a b].tally
p counts.max_by { |_, n| n }
$ rugaroo run greet.rb Go
1. hello, Go
2. hello, Go
3. hello, Go
["a", 3]

$ ruby greet.rb Go        # same output

The class becomes a Go struct, and its methods become ordinary typed Go (excerpt of rugaroo gen, minus //line directives):

type Greeter struct {
	name String
}

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
	})
}

Install

You need Go 1.26 or newer on your PATH: rugaroo calls go build to produce the binary. Ruby is not needed to compile programs (the Ruby parser, Prism, is embedded as WebAssembly).

go install github.com/jtarchie/rugaroo/cmd/rugaroo@latest

Or with Homebrew (brew tap jtarchie/rugaroo https://github.com/jtarchie/rugaroo then brew install rugaroo), or a binary from the releases page for Linux and macOS. Go is still needed either way, since rugaroo runs go build.

rugaroo run main.rb [args...]      # compile, build and run
rugaroo build -o prog main.rb      # write a binary
rugaroo gen main.rb > main.go      # print the generated Go
rugaroo test test/                 # run minitest files as one compiled program

New to rugaroo? docs/getting-started.md goes from a first program to porting your own scripts, with the common compile errors and their fixes. Or try it in the browser at the playground.

What you can use

Tier
Classes, modules, blocks, procs, exceptions, pattern matching, Struct/Data, threads and fibers, Ruby 4.0 syntax, reflection the compiler can answer (send, method_missing, const_get, instance_variable_get) stable
Core classes: String, Array, Hash, Integer, Float, Range, Set, Enumerable, Regexp, Time, File, IO, Kernel, ... (Array 87%, Hash 88%, String 77% of the ruby/spec examples that run; per class in docs/support.md) stable
Standard library: json, csv, date, time, net/http, webrick, socket, fileutils, optparse, logger, erb, bigdecimal, rexml, minitest and about 25 more stable
Gems compiled from source (rack, Cuba, pairing_heap; kramdown and liquid through -trace), fiddle, sqlite3, openssl (RSA verify only), Sorbet signatures, Ractor, --int-overflow=promote experimental
eval, define_method in your own code, in-place String mutation, encodings beyond ten not supported

docs/support.md lists every feature, core class and require name with its tier and the tests behind it. examples/ has one program per feature, each a normal Ruby script you can run with ruby.

What it can't do

rugaroo compiles a closed world: every class and method must be visible when the program compiles, and the binary has no interpreter. So there is no eval and no define_method in your own code (a gem's load-time define_method is compiled in). Strings are immutable Go strings, so << and upcase! are compile errors; build new strings or use StringIO. Integers are 64-bit and raise RangeError on overflow unless you opt into --int-overflow=wrap or promote. Hash#[] returns T?, so typed code checks for nil or uses fetch. Each of these trade-offs has a numbered decision in the design record; docs/support.md lists them with workarounds.

Performance and conformance

A compiled program does no interpretation. Over the workloads in benchmarks/ rugaroo runs about 3x faster than MRI; BENCHMARKS.md has the table, the comparison columns (YJIT, spinel, Crystal, hand-written Go) and where rugaroo trails.

Correctness is measured against MRI: every example, minitest suite and compile-error archive runs against ruby under go test. As a wider probe, cmd/rubyspec runs ruby/spec: 10,032 of its examples pass (8,916 in core, 70% of the core examples that run), and most of the rest are skipped for something rugaroo leaves out by design. docs/support.md has the rate for each core class.

Docs

  • Getting started: install, first program, annotations, warnings, tests, porting errors.
  • Support: what is stable, experimental or not supported.
  • Gems: compiling programs that load gems.
  • Command line: commands, flags and environment variables.
  • Design record: every trade-off, as numbered decisions.

How it works

  1. Parse the prelude and the program with Prism (the official Ruby parser, run as WebAssembly via wazero).
  2. Collect every class, module, method and constant into one closed world; resolve superclasses, mixins and RBS signatures.
  3. Infer local, instance-variable and return types by dry-running code generation.
  4. Emit Go: generics for containers and mixins, interfaces for virtual dispatch, iterators (iter.Seq) for yielding methods.
  5. Prune everything the program can't reach, gofmt, and hand the file to go build.

The core library is prelude/: Ruby source that reopens core classes, with leaf primitives dropping into Go through %x{ ... } bodies. Generated programs use only the Go standard library and never reflect; calls on untyped values go through switches generated from the whole program (docs/dynamic-dispatch.md).

Development

Running the test suite needs Ruby ≥ 4.0 (to produce the expected output), Bundler and golangci-lint on PATH:

bundle install          # rbs, rbs-inline, webrick, rackup
go test ./...           # every example and test file, compared against MRI
go test -run 'TestExamples/each/05_word_count' .    # one example

There are no golden files: MRI's output is the only expectation. Each examples/*/main.rb must pass rbs validate, the generated Go must pass gofmt, go vet and golangci-lint, and the binary's stdout and exit code must equal ruby main.rb. Behaviour checks live in testdata/test/*_test.rb as minitest files that must pass on both MRI and rugaroo. A cold run can take more than ten minutes; pass -timeout 30m.

cmd/rugaroo/          the CLI
internal/compiler/  Ruby → Go: declarations, type inference, codegen
internal/rbs/       the RBS type-syntax subset the compiler understands
prelude.rb, prelude/  the core and standard library, in Ruby (+ Go helpers in prelude/go/)
examples/           one program per feature, each checked against MRI
testdata/           minitest suites, print-and-compare snippets, compile-error cases
docs/               user docs, design record and deep dives

CLAUDE.md has the full command list and contributor conventions.

Prior art

Opal (Ruby → JavaScript, core library in Ruby with %x{} escapes), Crystal (Ruby-like syntax, static types, same 64-bit Integer trade-off), and TruffleRuby/Rubinius (core written in Ruby over primitives).

License

MIT. The minitest port in prelude/minitest.rb keeps minitest's own MIT license (prelude/minitest.LICENSE).

Documentation

Overview

Package rugaroo compiles a typed subset of Ruby to Go.

Index

Constants

View Source
const GoMod = "module gen\n\ngo " + GoVersion + "\n\nrequire (\n\tgithub.com/dlclark/regexp2 " + regexp2Version + "\n\tgithub.com/ebitengine/purego " + puregoVersion + "\n)\n"

GoMod is the go.mod generated code builds in: the runtime modules a program may import (regexp2 for patterns RE2 cannot match, decision 242; purego for Fiddle, decision 326); Go loads one only when main.go imports it.

View Source
const GoVersion = "1.25.0"

GoVersion is the go directive for the module generated code builds in.

View Source
const StubsPath = "prelude/go/0_stubs.go"

StubsPath is where PreludeStubs' output goes, relative to the repo root: the only prelude/go file the embed pattern leaves out.

Variables

View Source
var GemSigs embed.FS

GemSigs holds vendored `.rbs` signatures for the gems rugaroo compiles (decision 160), under sig/gems/<gem>/. A gem program (one that requires a gem, so its load phase runs under MRI at compile time, decision 161) reads them to type the gem's otherwise-unannotated classes.

View Source
var Prelude embed.FS

Prelude holds prelude.rb, its require_relatives, prelude/go/*.go (pure-Go helpers embedded directly; see loadPreludeGo), and the vendored gems' lib files (vendored/<name>/, mirrored from the gems/<name> submodules by scripts/vendor-gems so the module proxy has them; pure Ruby compiled as user code when required: vendoredLibs, decision 328). The character class leaves out prelude/go/0_stubs.go, the generated type-checking stubs (decision 154): helper files must start with a letter.

Functions

func Compile

func Compile(ctx context.Context, mainName string, src []byte, loadPath ...string) ([]byte, []string, error)

Compile transpiles the Ruby source of mainName (with the embedded prelude) into a single Go file. loadPath is `ruby -I`'s: directories a `require` searches for the program's own files.

func CompileFiles

func CompileFiles(ctx context.Context, files []File, loadPath ...string) ([]byte, []string, error)

CompileFiles transpiles several Ruby files into one Go program, as Ruby loads them into one process: in order, each file's top level running before the next's (decision 84).

func CompileTrace

func CompileTrace(ctx context.Context, main string, runs [][]string, rubyOpts []string, loadPath ...string) ([]byte, []string, error)

CompileTrace runs main under MRI once per args in runs (rubyOpts first) and compiles what those runs loaded (decision 296).

func CompileTraced

func CompileTraced(ctx context.Context, entryName string, src []byte, main string, loadPath ...string) ([]byte, []string, error)

CompileTraced compiles an entry file listing the requires MRI loaded (decision 197), then the program's main file (the CLI's -main, decision 288). The -I dirs are gem dirs.

func CompileWithGems

func CompileWithGems(ctx context.Context, mainName string, src []byte, loadPath ...string) ([]byte, []string, error)

CompileWithGems is Compile for the CLI: a require only an installed gem answers is looked up under MRI and the program compiled with the gem (decision 173); a program without one never starts Ruby.

func GoSum

func GoSum() string

GoSum is go.sum for GoMod: its modules' lines from rugaroo's own.

func PreludeStubs

func PreludeStubs(ctx context.Context) ([]byte, error)

PreludeStubs is prelude/go/0_stubs.go's content (decision 154): what the generated program declares and prelude/go names, bodies replaced by panic(0), so the helpers type-check under `-tags rugaroo_prelude`.

func UserCode

func UserCode(code []byte, mainName string) string

UserCode is the part of Compile's output that mainName produced, for display: decls a `//line mainName:` directive precedes, of a decl with one inside (the top-level function) the part from there on, and the plumbing generated for mainName's types, found by name (X, XI, NewX, X_..., super_X_, receivers of those).

func WriteModule

func WriteModule(dir string) error

WriteModule writes GoMod and GoSum into dir.

Types

type File

type File struct {
	Name string // as given, relative to where the program runs: it is what messages and failure locations show
	Src  []byte
}

File is one Ruby source file of a multi-file program.

type Inference

type Inference = compiler.Inference

Inference carries parameter types inferred from use (docs/design.md decision 146) between compiles of nearly the same sources.

func NewInference

func NewInference() *Inference

NewInference is an empty Inference for CompileTestsSkipping.

type SkippedTest

type SkippedTest = compiler.SkippedTest

SkippedTest is a test method CompileTestsSkipping could not compile and turned into a skip.

func CompileTestsSkipping

func CompileTestsSkipping(ctx context.Context, files []File, seed *Inference) ([]byte, []string, []SkippedTest, error)

CompileTestsSkipping is CompileFiles for running what a test suite can: each test_ method (an `it` included) that fails to compile becomes a minitest skip carrying the error instead of failing the build. seed, when not nil, carries inferred parameter types to the next call for the same sources, which then starts from them.

Directories

Path Synopsis
cmd
rubyspec command
Command rubyspec runs ruby/spec directories under rugaroo, one minitest program each, so an example rugaroo can't compile is skipped with its error instead of losing the directory.
Command rubyspec runs ruby/spec directories under rugaroo, one minitest program each, so an example rugaroo can't compile is skipped with its error instead of losing the directory.
rugaroo command
Command rugaroo builds or runs a typed Ruby file as Go, like `go build` and `go run`, or prints the Go it compiles to.
Command rugaroo builds or runs a typed Ruby file as Go, like `go build` and `go run`, or prints the Go it compiles to.
rugaroo-wasm command
Command rugaroo-wasm is rugaroo's compiler for a browser (the static `rugaroo web`, decision 102): it defines rugarooCompile(src) and stays alive for calls.
Command rugaroo-wasm is rugaroo's compiler for a browser (the static `rugaroo web`, decision 102): it defines rugarooCompile(src) and stays alive for calls.
Package examples embeds examples/*/main.rb for `rugaroo web`'s example picker, kept out of the rugaroo library so its importers don't carry them.
Package examples embeds examples/*/main.rb for `rugaroo web`'s example picker, kept out of the rugaroo library so its importers don't carry them.
internal
boot
Package boot runs a Ruby program's load phase under MRI at compile time and records what the source alone does not show: methods built by define_method or a string eval, the files autoload actually loaded, the classes and their ancestors, and the objects held in class state (docs/design.md decision 161).
Package boot runs a Ruby program's load phase under MRI at compile time and records what the source alone does not show: methods built by define_method or a string eval, the files autoload actually loaded, the classes and their ancestors, and the objects held in class state (docs/design.md decision 161).
compiler
Package compiler turns typed Ruby (rbs-inline) into Go.
Package compiler turns typed Ruby (rbs-inline) into Go.
rbs
Package rbs parses the subset of RBS type syntax that rugaroo understands.
Package rbs parses the subset of RBS type syntax that rugaroo understands.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL