On stage at Rails World this year, DHH said something I didn’t expect to hear from the creator of Rails:

“I don’t know any Rust at all. I consider that a feature […]”

At 35:40 in the keynote, he adds that he evaluates the Rust code as a black box, from the outside, the way any business owner who commissions programmers would.

In early October the chart arrived. Campfire, the 37signals chat app, rewritten by agents in seven languages, with Rails near the bottom.

Bar chart of the Campfire room page in requests per second: Django 1,507; Laravel 3,872; Rails 4,101 (highlighted); Elixir 5,350; Express 42,636; Go 53,060; Rust 106,494; C 137,524.
Campfire room page, requests per second. Source: the once-campfire-rust README.

The timeline did the math on its own: if the creator of Rails doesn’t need to know Rust to ship a Campfire 26x faster, language has become an output format. Pick the fastest one and move on.

That reading is half right. The right half is Rust. The wrong half is crediting Rust with the achievement.

1. Speed isn’t the news

The numbers are real, and they shouldn’t surprise anyone. Rust is compiled, Ruby is interpreted. A Rust binary serving HTTP will be much faster than Rails, agents or not.

And if nobody is going to read the code, picking the fastest language makes complete sense. Rust is a good language. Treated as a black box, it delivers performance without charging you the cost of writing Rust. That’s what DHH is doing with the HEY backend.

NOTE: among the other ports, the math is less clean. Today once-campfire-rust has 437 commits, the Go port has 40 and the Elixir one has 45. Part of the gap between Rust, Go and Elixir is tuning rounds, not language. The gap between Rust and Rails, on the other hand, really is language.

What’s new is that someone who says he knows no Rust shipped a Campfire in Rust that, according to its own repository, can’t be told apart from the original, pixel for pixel.

2. What Rust does in Campfire

Campfire is the 37signals group chat: rooms, direct messages, attachments, search. It’s sold under the ONCE model - you pay once and run it on your own server. The code is Rails, open source, MIT licensed.

An install runs several processes. Ruby with Puma serving HTTP, Redis, Resque for background jobs, and Thruster in front handling TLS and caching.

The Rust port replaces all of that with a single executable. The frontend is still the Rails one, with a few tweaks. Rust is the entire server: HTTP, Action Cable-compatible WebSockets, jobs, push, search.

And there’s a requirement that changes the size of the problem. The README promises it uses the same SQLite database, the same file layout and the same signed and encrypted cookies, “so existing installs can upgrade without migrating data or signing everyone out”.

In other words, the Rust version has to behave exactly like Rails, down to the details nobody sees.

How does someone who doesn’t know Rust guarantee that?

3. The most important code in the port was written in Ruby

The repository answers. Before any line of Rust, there’s a plan in plans/rust-conversion.md, and its goal is parity, not speed: a Campfire in Rust that “can’t be told apart from the Rails app”.

To prove it, they built a harness in two halves:

  • Visual capture, in TypeScript with Playwright. It compares HTML, DOM, accessibility tree and screenshots of both apps.
  • Golden vectors, in Ruby. Signed cookies, encrypted cookies, CSRF, signed IDs, Turbo stream names.

The first half is one black box looking at another through a browser. The language there doesn’t matter.

The second half is the answer key, and the rule for it is in the header of reference-tools/rails_compat_vectors.rb:

“Every expected value comes from the reference app itself (cookie jar, session store, controllers, verifiers), never from a reimplementation.”

The plan is even blunter: “We never port one from our reading of the docs.”

Could that answer key have been written in Rust? Partly, yes. You boot Rails, log in over HTTP, capture the Set-Cookie and save it. That works from any language.

But a lot of what has to match doesn’t show up over HTTP. How the key is derived from secret_key_base. Which digest signs the cookie. How an old, rotated key keeps working. To check those from the outside, you’d have to reimplement those parts of Rails inside the harness - and then it’s Rust checking Rust. Both sides agree, both can be wrong together, and every existing user gets logged out on deploy.

The port already has that reimplementation, by the way. It’s a crate called rails_compat. What it needed was an answer key that didn’t come from the same head.

In Ruby, the script runs inside the reference app and asks Rails’ own cookie jar and verifiers directly. It’s the shortest path to the right answer.

Diagram of the Campfire parity harness. On the left, Campfire on Rails as the oracle; Ruby scripts ask Rails and generate golden vectors (signed cookies, CSRF, signed IDs, Turbo streams), which check the Campfire in Rust written by agents. A loop of Rust checking Rust is crossed out with a red X. At the bottom, Playwright in TypeScript compares HTML, DOM and screenshots of both apps.

To write those vectors, someone had to know which contracts exist. That the cookie signing digest is separate from the key derivation digest, and may be SHA1. That an image variant’s name is the hash of a Ruby Marshal dump. That Rails has to be able to boot on the database Rust wrote, because rollback has to work.

That’s what holds the whole port up, and none of it shows up in req/s.

4. What transferred was the model of the problem

Most of the port’s commits carry Co-Authored-By: Claude. Who typed the plan matters little. The plan encodes deep knowledge of Rails and Campfire - and that knowledge is what lets someone drive agents in a language they don’t know.

Think of an experienced cook in a stranger’s kitchen. They don’t know which drawer has the ladle, or how that stove heats. But they know what the sauce has to taste like. Taste, adjust, taste again.

DHH doesn’t know where Rust keeps its pans. But he knows how Campfire has to behave better than anyone on the planet.

This works well with agents because LLMs are very good at reproducing - that’s how they were trained. “Look at this text and, when I ask, generate a similar one.” With a good reference at hand, they get much more right.

Campfire on Rails was that reference. Every screen, every cookie, every response had an original to compare against.

I believe Omarchy works the same way. DHH knows what he wants to build and knows how to check the result against a reference. Rust there is a choice made by the artifact, not what keeps the project moving.

That’s why, whenever I start a project, the first thing I do is look for references: similar projects, code from people who already solved something close to what I want. It helps a lot in steering the agent.

5. And it isn’t DHH’s privilege

This is where I disagree with the miracle tone.

Any senior carries models of problems they’ve seen in other languages. When you sit down to solve something, you already have a picture of what the solution looks like. AI closes the syntax gap. You brought the rest.

I did this twice this year.

BEThoven, the World Cup betting pool we played in the terminal over SSH, is written in Go. I knew Go’s syntax and a piece of the standard library. That’s it. I picked Go because I needed a good TUI served over SSH, in a single binary, and the Charm ecosystem (Bubble Tea, Wish) solves exactly that. The pool held up for the whole Cup.

I had never written a line of Rust. Still, I have patches merged into ai-memory, a sizable Rust project. I sent five PRs, three went in.

Neither case makes me a Go or Rust specialist. I didn’t need to be.

6. But there’s a ceiling

“Any senior can do it” has limits, and it’s worth knowing where they are.

Judgment in the language doesn’t transfer. I can tell whether a Rust patch is correct: the behavior matches, the tests pass, the design makes sense. I can’t tell whether it’s idiomatic Rust. The final word on that belongs to whoever maintains the project and lives in the language.

But you can shrink that distance, and again with a harness. A context file describing how the project writes code, a well-configured linter, CI that fails on warnings. once-campfire-rust itself does this: unsafe_code = "deny" in Cargo.toml, an AGENTS.md telling agents to keep clippy clean, and CI running clippy with -D warnings.

The judgment of people who live in the language becomes written rules. And agents follow written rules.

Domain weighs more than language. DHH said in the keynote that the HEY backend is becoming a mail server in Rust. The hard part of a mail server is SMTP, deliverability and security. Rust is the cheap part. The model of the problem only transfers if you have the model.

Review bandwidth is the real ceiling. DHH said on stage that he wrote 150,000 lines in August. No senior reviews that. At that throughput, the harness stops being a detail and becomes the only thing between you and production. The port’s own overnight report has an agent that sent the user’s email in the User-Agent of a request to crates.io. It was small, and we only know because the report wrote it down.

Operating doesn’t transfer as fast as writing does. Profiling, a panic at 3am, knowing which crate the ecosystem has already abandoned. That takes time, with or without agents.

7. “Retired from being a professional programmer”

In the same keynote, at 39:21, DHH said he has retired from being a professional programmer, some four or five months earlier.

I have no problem with that. I actually think everyone will quit programming the way we used to do it. Me included.

But look at what took its place, in his own repository: a plan reviewed by another model, contracts listed one by one, vectors generated from the reference app, and rollback tested before the first feature.

That’s the senior work left over once typing left the table.

Akita puts it well in a recent post (in Portuguese, translation mine):

“QA has become the programmer’s most valuable skill, far above ‘knowing Rust syntax’.”

The chart sells the wrong row. What should go viral is reference-tools/.

8. Languages are becoming more and more what they set out to be

It’s always been this way: each language sets out to solve a kind of problem, and the artifact picks it.

What got in the way was the cost of getting there. Learning the syntax, typing, failing, until you got productive. So a lot of people ended up using the language they already knew, not the one the problem asked for.

Now that typing is cheap, that friction has dropped. And the choice falls more and more to the artifact.

DHH is building Omarchy, an operating system. Rust makes complete sense there. Anyone shipping binaries - CLIs, agents, servers that need to fit in a small container - has good reasons for Go or Rust. I went with Go because I wanted a TUI over SSH in a single binary.

And for a new web app? Akita himself, in the same post:

“Personal example: new web app, I open Ruby on Rails without thinking twice.”

Every language has its place, and now you can get there without paying the syntax toll.

What you can’t do is skip the problem toll.

Closing

“I consider that a feature.”

It is a feature. But the feature isn’t Rust. It’s knowing Campfire so well that you don’t need to know Rust - knowing what you want and having something to compare against.

Any senior can build that feature. It just doesn’t come for free, and it doesn’t show up in a req/s chart.

Thanks for reading!