Back to all posts
22 min read

The Rust Revolution Proposal Had a 363-Day Fuse

The Rust Revolution Proposal Had a 363-Day Fuse

On September 23, in Austin, David Heinemeier Hansson opened Rails World by explaining how he feels about a programming language.

“I hate Rust with a passion. Rust to me is like pouring acid in my eyes when I have to look at the code.”

He called it the ugliest programming language invented in probably the last 40 years and put some on a slide labeled Exhibit A. About a minute later he reversed direction:

“I love Rust! Rust is amazing if you never, ever, ever have to look at it yourself.”

The news under the punchline was that HEY is leaving the web. HEY is the email service Hotwire was extracted from. 37signals kicked off six native apps for it a week before the talk, and the Rails application behind them is being turned into a Rust mail server, written by agents. David will not be reading it. “I don’t know any Rust at all,” he said. “I consider that a feature.”

On September 25, 2025 at 10:28 UTC, 363 days before that keynote, I committed a file called RUST-REVOLUTION-PROPOSAL.md to a Rails fork run by Supreme Leader Kim Jong Rails. It proposed rewriting Rails in Rust. It was a joke, and I picked Rust for one reason: it was the language David would never touch.

For the record, I have been shipping into the same repository as David for twelve years. I am #65 on the Rails contributors list, with 211 commits since May 2014. He is #4. When I mock Rails, I am mocking a codebase with my name in it.

Derails

September 2025 was a bad month to be a Rubyist. On the 19th I published The RubyGems Coup, about Ruby Central removing the people who built RubyGems. Around the same time, emails started arriving that invited me to join an effort to get DHH off Rails. As the emails told it, the goal was to take the spotlight away from David, and from Jason Fried and Tobi Lütke with him.

I have spent some times on this blog complaining about parasites, people who never contributed to a project and show up to tell its maintainers what to change. You know the type from the master to main era. Someone with zero commits opens a pull request because a word offended them, and I get to rewrite a hundred workflows. The emails read like that pull request at the scale of a whole framework.

David says controversial things on his blog. That is David talking, and I don’t care.

So I asked what the fork would be called. Forking Rails made no sense to me. Anyone who wants Ruby without David can go to Hanami, or to Trailblazer, which I am part of, and most gems work across frameworks anyway.

What they were after was the community. Rails has absorbed a rival before: in December 2008 David announced that Merb was merging into Rails 3, and Merb’s people came with it. This plan ran that merge in reverse. Move enough of the ecosystem out and the users follow. I maintain state_machines-activerecord and state_machines-activemodel, each downloaded more than 65 million times. Picture me archiving both to spend my time on other frameworks. Multiply that across the ecosystem and add a marketing push. Eventually some team decides that everybody moved, so they should move too.

The model was ReiserFS. After Hans Reiser was convicted of murdering his wife in 2008, nobody wanted to maintain his filesystem. Namesys went out of business that year, and Linux finally removed ReiserFS in 6.13 for lack of an upstream maintainer. The plan was to get the ReiserFS ending for Rails out of two blog posts.

When the effort went public on September 22 as Plan Vert, its letter asked the Rails Core team itself to cut ties with David and do the forking.

The letter’s own notes explain the name: Plan Vert was a railway sabotage campaign by the French Resistance. The occupation authorities called the people doing it terrorists. History calls them the Resistance, because France won. A campaign named after derailing trains had come for Rails, so I named the org Derails.

It went up at 07:39 UTC on September 25 under the tagline “Confusion over Configuration.” By midnight it held 78 commits from a revolutionary committee: Kim Jong Rails, Vladimir Pushin, Bashar al-Code, and Xi JinPingPong. Between them they replaced ActionMailbox with InactionMailbomb and decreed, by formal RFD, that all deployments travel by train.

Every one of those dictators was me. The imagination was mine, and Claude Sonnet did most of the typing. The fork admitted as much on day one. Eighteen minutes before the Rust proposal, Bashar al-Code committed an AGENTS.md, symlinked CLAUDE.md to it, and explained why in the commit message:

Can’t remember Ruby after months in refuge

Claude actually writing this commit

Vladimir reverted it seven minutes later for a security breach, because Bashar had mentioned his MacBook. The confession is still in the git history.

So the character who proposed rewriting Rails in Rust was a deposed dictator hiding in Moscow who had forgotten Ruby and handed his keyboard to agents. I wrote him as a joke. A year later his workflow was the opening keynote.

Derails called one more thing. The repo description said PostgreSQL-only, and at 13:39 UTC Vladimir committed ”🐘 POSTGRESQL SUPREMACY: All inferior databases ELIMINATED.” I meant that one. Call it the rise of the civilized database.

Rails 8 had pitched SQLite as a production database, and the mood was David’s own catchphrase applied to operations: look at all the DevOps I am not doing. I think SQLite in production is slop. Everything lives in one file, so one corrupted sector or one dead disk takes the whole building down. PostgreSQL is one of the best databases ever built. It is not named after anybody’s children, and it has no BDFL.

A year later the surprise is who followed. Alex Shapalov’s bio now reads “Building Postgres for agents,” above a row of pg-prefixed domains. I had already made the move myself: kaunta, my analytics server, requires PostgreSQL 18 or newer, and there is no setting for “what if I want to use something else.”

Why Rust

I wanted to write it for Go first. Go had one problem as a punchline: 37signals was already using it. Thruster, the HTTP/2 proxy that new Rails 8 apps ship with, is Go. So is kamal-proxy, which Kamal 2 deploys run through. A proposal to move Rails toward Go could have landed at 37signals as a sensible plan.

Go survived in the fork as a contingency. At 12:48 UTC Vladimir committed a go.mod with the message “Backup plan if Rust goes rogue.”

Rust was the opposite case. I was deep into learning it at the time, and I wanted derails to be a fork that could never merge back into this timeline. My reasoning was simple. Even if David ever went compiled, he would pick Crystal, Elixir, Odin, or Zig. He would never pick Rust.

The paper trail agreed. In April 2024 David had posted:

I’d rather stop programming before writing biz logic in Go or Rust!

Six weeks later he wrote that he “wouldn’t be a happy camper if I had to spend my days programming Rust,” while loving the tools its community builds.

Satire has a safety rule. Pick the tool your target would never adopt, or you are writing a roadmap by accident. I followed the rule, and I meant never, ever. On September 23 David used the same words about Rust, with a different ending.

The Scorecard

Poe’s law says that without a clear signal of intent, parody of an extreme position is indistinguishable from the real thing. I signaled as loudly as I could. The proposal asked who controls Ruby’s garbage and called the borrow checker the KGB for your variables. Here is how it held up.

The proposal, September 25, 2025The keynote, September 23, 2026
“Phase 1: Rewrite core in Rust, keep Ruby wrapper”“for Hey, we have our answer. It looks like native applications on the frontend and Rust on the backend.”
“Actually achieves 60km/h+ processing”“99% less CPU, 95% less memory.” Peak traffic “could probably be served on a single Raspberry Pi.”
“Code reviews by borrow checker (incorruptible)”“I evaluate the Rust box as a black box on the outside”
“Q: Isn’t Rust hard to learn? A: Revolution is not a dinner party.”“I don’t know any Rust at all. I consider that a feature.”
“R-AI-LS was ALWAYS AI framework!” (commit, same afternoon)“Convention over configuration leads directly to things like token efficiency.”
RFD-1337, step 5: “DHH ‘retires’ to Pyongyang → Mission complete”“I have retired from being a professional programmer.”

The Pyongyang part did not happen. I hope.

To be fair to the keep-Ruby-wrapper row, David did not declare Rails dead. He said web-shaped applications like Basecamp still belong on the web, and that Rails is well positioned for them. HEY is the one that left.

The borrow checker row is the one that should worry people, and I will come back to it. First, the line I got wrong.

The Line I Got Wrong

Phase 2 read: “Force everyone to learn Rust (re-education).” The FAQ doubled down: “Q: What about the Ruby developers? A: Re-education camps teaching Rust lifetimes.”

That was the joke. Rewriting Rails in Rust was absurd because of the bill it sent to humans: every Rubyist dragged through lifetimes and trait bounds by hand, people who had picked Ruby precisely to stay out of that fight. The satire was safe for exactly as long as that bill was real.

Nobody went to the camp. The agents went. David described the arrangement on stage:

Agents like Rust. Great! What a division of labor. I’ll tell you what to do. You’ll write it in Rust. I never have to look at it.

Read the 2024 quotes again with that in mind. “Spend my days programming Rust.” “Writing biz logic.” Both were about a human in the chair. Neither was about the binary. Take the human out and the objection has nothing left to stand on.

This is the part that outlives both of us. Satire used to be protected by cost. If the thing you mocked was expensive enough, nobody did it, and the joke stayed a joke for a decade. Absurd engineering was priced in human years. Agents reprice it in tokens. The TerminalTextEffects rewrite David posted in August took 11 million of them. My unmergeable fork lasted a year.

The one person who actually served Phase 2 was me. Thirteen days after the proposal I made the first commit of state-machines-rs, a Rust port of the state_machines gem I maintain. That first commit is 18 files and 2,383 lines, which from the outside looks like an LLM dropped it in one go. It is more than 200 local commits squashed into one. Nobody needed to watch me learn Rust one commit at a time, least of all the one where I checked in target/ by mistake and left it tracked for a while. Its README calls it “a learning platform for Rubyists transitioning to Rust.” I wrote it for myself first. The re-education camp had one inmate, and he volunteered.

Exhibits A and B

Here is Exhibit A, from the slide at 29:13.

Exhibit A from the Rails World 2026 keynote: a hand-written async timeout middleware in Rust

He chose well. This is a hand-rolled async timeout middleware, the kind of plumbing that sits under Rust web servers, and it is about as ugly as Rust gets. The return type alone takes a full line of angle brackets.

Each piece of that type is a promise the compiler holds you to. Send says the future can move between threads, and 'a says it will not outlive the borrow of self. It is also written the long way. The ecosystem has names for both halves: BoxFuture from the futures crate and BoxError from tower. This is the same return type, and the compiler accepts the swap in both directions:

pub fn call<'a, S, B>(&'a mut self, req: Request<B>) -> BoxFuture<'a, Result<Response<B>, BoxError>>

The Ruby version of the same middleware is a Rack class:

class TimeoutMiddleware
  def initialize(app, duration)
    @app = app
    @duration = duration
  end

  def call(env)
    ::Timeout.timeout(@duration) { @app.call(env) }
  rescue ::Timeout::Error
    [504, {}, []]
  end
end

It is shorter because it hides what the Rust spells out. Timeout.timeout works by raising an exception inside the running thread at whatever line it happens to be on, which is why Mike Perham called it Ruby’s most dangerous API and told people to remove it from their codebases. The Rust version can only be cancelled at an .await, and the destructors still run.

So Exhibit A is honest ugliness. Every bracket in it pays for something the Ruby version leaves to luck.

Application code is where the two languages start to look alike, and I have a year of receipts for that. Here is Exhibit B, from my port. The Ruby gem first:

class Vehicle
  state_machine initial: :parked do
    event :ignite do
      transition parked: :idling
    end

    event :park do
      transition idling: :parked
    end
  end
end

Then the crate:

use state_machines::state_machine;

state_machine! {
    name: Vehicle,
    initial: Parked,
    states: [Parked, Idling],
    events {
        ignite {
            transition: { from: Parked, to: Idling }
        }
        park {
            transition: { from: Idling, to: Parked }
        }
    }
}

Nobody’s eyes burn reading that. It is the Ruby DSL wearing braces.

The difference shows up when you try to park a car that is already parked. Ruby answers at runtime:

car = Vehicle.new
car.park   # => false
car.park!  # Cannot transition state via :park from :parked (StateMachines::InvalidTransition)

Rust refuses to build the program:

error[E0599]: no method named `park` found for struct `Vehicle<(), Parked>` in the current scope

Each state is its own type, and a parked Vehicle has no park method to call. That is the typestate pattern, and it is the best argument for Rust I know. A business rule moved out of runtime and into the type checker, where that particular mistake cannot ship.

Rust written by someone who reads it looks like Ruby’s stricter cousin. Closures take their arguments between pipes the way blocks do, and the last expression is the return value.

Exhibit A was the worst case. The everyday case is whatever comes out of the agent, and he told the same audience how much of that he lets through:

I allow the agent to just spit out more than was necessary, in a way I would never tolerate from my Ruby code.

He wrote 150,000 lines in August. His long-run average was about 30,000 lines of Ruby a year, and he credited a lot of the gap to “that awful Rust code I don’t want to look at.” The ugliest language of the last 40 years is, in his shop, the only one exempt from his taste.

What the Compiler Does Not Check

The proposal had a governance section. “Decisions made by Rust compiler (impartial judge).” “Code reviews by borrow checker (incorruptible).” The conclusion ends on “Democracy → Compiler Dictatorship.” I wrote those as jokes about the Rust community’s faith in rustc.

On stage David described the same arrangement in earnest: “I evaluate the Rust box as a black box on the outside, as any business owner in history who’s ever commissioned a group of programmers to do anything for them.”

Credit where it is due, twice. First, he picked the right material. In The Republic of Slop I called Ruby playdough and Rust cement, and a mail server that has to sit on a box for years asking nobody for anything is cement work. Second, he can say what the cement costs: 99% less CPU and 95% less memory. Only the Raspberry Pi line was back-of-the-envelope by his own description, and even that is more measuring than most of the republic ever does.

The problem is jurisdiction. The borrow checker is a strict judge with a narrow court. It proves memory safety and rules out data races. My proposal promised “No data races (only class struggle),” and the first half is a real guarantee. It has no opinion on whether a message landed in the right inbox, or whether a deleted email is actually gone. In a mail server, those are the bugs that matter.

Exhibit B cuts both ways. The compiler rejected park on a parked car because someone wrote that rule into the types. It enforces the rules somebody encoded and nothing else. When nobody reads the code, nobody knows which rules got encoded and which got approximated, and a black box evaluated from the outside passes every test somebody thought to write.

The business owners David is comparing himself to had one thing he has given up. They could point at a line and ask a programmer why it was there. The programmer either remembered or went and found out, and could be held to the answer. An agent will answer the same question in confident paragraphs, and what you get back is a narration of the code instead of a memory of the decision. David named the trade himself, in the most honest sentence of the keynote: it is “a privilege to have no fucking clue what is being outputted by these agents into the Rust box.”

Reading was never the scarce resource. Authority is. In the keynote model it belongs to rustc and a test suite, and reading has been reclassified as a cost to avoid.

Unlimited Tokens

On September 26, r/rails asked the question outright: “Is DHH just.. straight out lying?” The post set the keynote next to a Rails Foundation benchmark and a blog post from inside 37signals, and concluded that none of it adds up. The three most upvoted replies were about his character.

It adds up, and the explanation was on GitHub the same day.

Two days after “Rust is amazing if you never, ever, ever have to look at it yourself,” David opened pull request #35 on ttfx. It adds an x86-64 assembly engine for all 37 effects: 47,674 lines of NASM across 187 commits, 130 of them co-authored by Claude Opus 5.5. A Codex review found three bugs and one leak. Rust stays on as the front end, and as the fallback on machines the assembly cannot run on. It merged on September 26. My proposal stopped at Rust. I lacked ambition.

I was one of the reviewers. I approved it with a line borrowed from Mitchell Hashimoto, “The pursuit of excellence needs no justification,” and called it the second-fastest screensaver in the universe, right after RollerCoaster Tycoon. Then I asked for the merge to wait while I checked something. The PR had tested its oldest CPU tier only under emulation. I built it on a Core 2 Duo T7500, a laptop chip from 2007, and approved again once it ran.

Two hours later a commenter asked why anyone would trade portability for a speedup nobody can see. David answered with the same Hashimoto line, in quotation marks:

“The pursuit of excellence requires no justification”. And if you’d look at the code, you’d see that we fall back to the slower Rust implementation when asm acceleration is not possible.

Also, you’re still stuck in fixed-pie thinking if you think this is happening at the expense of anything else. I have unlimited tokens. We can fix everything, we can do anything.

“If you’d look at the code” is advice from the man who calls not looking a privilege.

“I have unlimited tokens” is half of the answer to the Reddit thread. My reply there was that he is not lying. He has unlimited tokens, and we don’t. By his own math the first Rust port of TerminalTextEffects cost roughly $550 in Fable tokens, since Grok repeated it from Fable’s plan for around $55, “about 1/10 the cost of the Fable implementation.” The cost that kept my joke a joke is, for David, zero.

The other half is the oracle. Every step of ttfx had one. ChrisBuilds started TerminalTextEffects in Python in 2023. The Rust engine is a parity-exact port of it, and the assembly is held byte for byte to the Rust: the PR’s oracle script compares the output and exit status of the two engines across its test cases. Each rewrite is graded against the one before it, and the root of the chain is a human’s Python.

In the thread I called LLMs perfect monkeys: “Show them something that already exists, and they can reproduce the pattern in almost any language they got trained on.” HEY is something that already exists. The Rails app is what the Rust backend is being ported from, down to the spam-fighting Account::Reputation::Cluster that Jorge Manrubia had Claude add in Ruby a month before the keynote. Jorge’s own caveat: “I am not saying you can blindly merge code now.”

Take the oracle away and the same machinery looks different. The Rails Foundation’s Agents on Rails benchmark hands models 20 feature tickets on Fizzy, 37signals’ kanban app, “written the way a product person files them.” A run counts only if it passes the app’s own test suite and the ticket’s hidden checks. This is the harder benchmark David mentioned on stage, which he said replaced a first version that agents had saturated at 95%. The best result is GPT-6 Astra at maximum effort, with 53.3%. Claude Opus 5.5, the model on most of PR #35, passes 33.3% at medium effort.

Same model. Given an oracle, it produced 47,674 lines of assembly that match the Rust byte for byte. Given a ticket, it ships one run in three.

So the Reddit numbers and the keynote numbers are both true, because they measure different jobs. Porting is the job that comes with an answer key, and agents ace it. HEY’s next spam wave will not come with a reference implementation.

The Lesson

I have announced Rust in Ruby land before. It went differently for me.

While I was learning Rust, I built ActiveLLM, a Rails engine for wiring language models into an app, and gave it a Rust companion: a small proxy that an LLM wrote for me. I shipped one version in March 2024, eleven months before anyone called that vibe coding, and went to Reddit to present it. The replies insulted me for the Rust, and for letting a machine write it. It felt like preaching in r/atheism.

Then it left the thread. I got emails, and some of the people involved, influencers mostly, contacted my employers. My u/seuros account was banned soon after, with help from a separate fight I was already having in another subreddit over a crypto scam. These days I post as u/TheAtlasMonkey. I deleted everything in the ActiveLLM repository except two commits and made it private.

The Rust half never died. TensorZero, a funded Rust LLM gateway, covered the same ground at a much larger scale. When its founders wound the project down in June 2026 and archived the repository, I forked it and moved my own work on top. It now carries 763 of my commits. I named it the LLM Service Daemon. Claude kept calling it LSD, so now it is LSD.

What upset them was the order of events. I got there first. They could have taken the idea and run with it. They pushed me out instead.

It happened again with chaos, my agent harness, written in Rust. Its description ends with “Everything is a Driver,” and that line drew most of the hate. In August 2026 DeepSeek released DeepSeek Harness, in TypeScript, under the tagline “Everything is a Plugin.” It had 237,147 stars six weeks later. The idea was fine. It needed a different noun and a bigger name on the README.

This week r/rails is doing the same thing to David. Whoever brings Rust to Ruby gets the character reviews.

The engineering lesson is the one I trust more. I have tried porting applications to Rust. It is hard work. The build artifacts eat the disk, and every change is a recompile. I kept going anyway and became the third-largest contributor to rama, a Rust framework for moving and transforming network packets. At this point I do not need an LLM to write Rust for me. I use one to review my code and to draft commit messages, and when the message it drafts runs long, that tells me I staged too much for one commit. When I want to build something quick that is not solid yet, I still type rails new.

HEY proves the same lesson from the other end. It ran as a Rails app for six years before the port began. Those years are where the shape settled and the Rails app turned into the answer key the agents now port against. Playdough until the shape stops changing, cement after. Nobody gets to skip the playdough, not even with unlimited tokens.

Oxidize Everything

The proposal ends with a conversion table:

Ruby → Rust
Rails → Ferrous-Derails
DHH → Irrelevant
Democracy → Compiler Dictatorship

Two of those arrived. Rails is still Rails, and I got the DHH line backwards. He is the most relevant person in this story. He made the joke real on his own stage, and he left the acid-in-my-eyes line in. Most people who change direction this hard do it quietly.

In April 2024 he wrote that he would rather stop programming than write business logic in Go or Rust. Thirty-nine minutes into the keynote: “I have retired from being a professional programmer.”

He kept his word. He stopped programming first.

🔗Interstellar Communications

No transmissions detected yet.Be the first to establish contact!

• Link to this post from your site• Share your thoughts via webmention• Join the IndieWeb conversation

Related Posts

The Republic of Slop

A country where no citizen has ever felt a constraint. The dev box has 128GB, the model never says "this is too much," and the cloud bills instead of refusing. Then it ships to a 2GB server. On languages as materials, and the 100x receipt that proves nobody measured anything.

AIengineeringslop