# Installing Ruby gems with go get

[[nesbitt-io|Andrew Nesbitt]]'s thought experiment: set `GOPATH` to a Ruby load path, put a `go.mod` in a gem's repo, and `go get github.com/rack/rack@v3.0.0`. Golang unpacks the module to `$GOPATH/pkg/mod/github.com/rack/rack@v3.0.0/`, you point `RUBYLIB` at its `lib/` directory, and `require "rack"` works. Ruby has no opinion about how the files arrived.

The reason this works at all is that Golang made three choices at naming, transport, and integrity that happen to be language-agnostic, and none of them look at what's inside the zip.

## Nothing checks that it's Golang

`proxy.golang.org` would like a `go.mod` at the repo root, but versions come from git tags and the `go.mod` doesn't have to describe valid Golang code. `sum.golang.org` hashes whatever zip it receives; it does not parse Golang, does not verify packages compile, does not care. People already exploit this — protobuf definitions, JSON schemas, Terraform modules, plain data files are all distributed as Golang modules with no Golang in them. Ratatoskr does a variant of the same trick, serving its own module identity over a mesh network (see [[ratatoskr]]).

The consequence Nesbitt is actually pointing at: your Ruby gem now inherits Golang's supply-chain properties. Every fetch goes through the caching proxy, the SHA-256 of the module zip is recorded permanently in a Merkle-tree transparency log, first fetch wins, and a maintainer who later rewrites a tag gets a hash mismatch that every Golang client refuses. RubyGems has no transparency log. `sum.golang.org` does. You've achieved better integrity for your gems by pretending they're Golang modules, and sidestepped rubygems.org while you were at it.

## Self-describing paths versus a central index

`import "github.com/foo/bar"` carries the host, the org, and the repo. Nothing resolves through an index. `gem install foo` — or `npm install lodash`, or `pip install requests` — is a magic string that means nothing without the registry that owns it. Central indexes buy short names and a place to file abuse reports; they cost governance, gatekeeping, and a single point of failure. Domain-based paths are squatting-resistant because squatting means owning the domain, and they're verbose enough that nobody wants to type `require "github.com/psf/requests"` at the top of every file.

Deno tried this with URL imports and retreated to JSR, a conventional registry with short names. The trade-offs are real and most communities have already decided they aren't worth it — the tooling would work, the migration wouldn't, since retrofitting means changing how everyone writes imports.

## What it would take to be Bundler

Recursion, mostly. Parse the gemspec, find dependencies, `go get` those, repeat, write `go.sum` as the lockfile. The dependency graph is the same graph Bundler computes; only fetching changed. There's a proof of concept at [andrew/go-bundler](https://github.com/andrew/go-bundler).

One structural difference would follow: Golang uses Minimal Version Selection. Ask for v1.2.0 and you get v1.2.0, not the newest thing that satisfies the constraint, which makes `go.sum` close to an afterthought. Bundler and most package managers prefer newest-matching, which is precisely why `Gemfile.lock` is load-bearing — without it you get whatever's latest today. MVS trades "always up to date" for "boringly predictable", and needs no SAT solving or backtracking. See [[default-version-bound-constraints]] for the related question of what a package manager writes into your manifest by default.

Two things break. Golang case-folds uppercase letters in paths because macOS and Windows treat `A` and `a` as the same file, so `BurntSushi/toml` lands on disk as `!burnt!sushi/toml`, and any Ruby tooling built on top inherits that. And native extensions: gems that shell out to `make` to build C have nowhere to do it, because Golang expects source or prebuilt binaries. Pure Ruby gems are fine.

## The point underneath the joke

Nesbitt frames this as part of a larger decomposition — a package manager isn't one system but six bolted together: naming, discovery, resolution, transport, integrity, and installation. Golang's unusual choices happen to sit in three of them that have nothing to do with any language, which is exactly why the Ruby hack is possible. If every ecosystem shared a content-addressed, transparency-logged, globally cached distribution layer, nobody would need to pretend anything was a Golang module. Instead Google built an anonymous package CDN with minimal governance and let anyone use it for free, and it now stores an unknown quantity of `.rb` files.

## Related

Same author as [[features-to-steal-from-npmx]], and the same underlying interest in registry design. The transparency-log argument connects to [[supply-chain-security]] and to [[reproducible-builds]]: an auditable hash is only worth as much as your ability to reproduce the artifact it names. [[dependency-vendoring]] is the opposite answer to the same anxiety — don't trust the distribution layer, commit the code.
