The Bool That Holds Go's Ecosystem Hostage
A single boolean in Go's runtime, if ever flipped, will intentionally break thousands of packages.
There's a single boolean in Go's source code that, if flipped from
falsetotrue, would instantly break thousands of packages. And the Go team put it there on purpose.
Wait, What's a Garbage Collector?
Before we dive in, let's make sure we're on the same page.
When you write code, you create variables - strings, objects, arrays. These live in your computer's memory. In languages like C, you have to manually tell the computer "I'm done with this, you can have the memory back." Forget to do that? Memory leak. Do it twice? Crash.
Garbage collection (GC) is the programming language saying "don't worry, I'll handle it." Go, Java, Python, JavaScript - they all have garbage collectors that automatically clean up memory you're no longer using.
Here's the twist: some garbage collectors are sneaky. They can move your objects around in memory while cleaning up, like rearranging furniture while you're not looking. Go's garbage collector currently doesn't do this - your objects stay put. But that's not a promise. It's just how it works today.
And that's where our story begins.
The Function That Does Nothing (But Means Everything)
Deep in Go's garbage collector code, there's a function that always returns false. It's been returning false since it was added, and it will probably return false forever.
But here's what makes it fascinating: if the Go team ever changes it to true, it will intentionally break thousands of programs.
func heapObjectsCanMove() bool {
return false
}That's it. That's the whole function. Three lines. Returns false. Done.
So why does it exist? And why would changing it break everything?
"An Unfortunate Idea That Had an Even More Unfortunate Implementation"
That quote isn't mine - it's straight from Go's source code. The Go team actually wrote that in a comment:
// heapObjectsCanMove always returns false in the current garbage collector.
// It exists for go4.org/unsafe/assume-no-moving-gc, which is an
// unfortunate idea that had an even more unfortunate implementation.
// Every time a new Go release happened, the package stopped building,
// and the authors had to add a new file with a new //go:build line, and
// then the entire ecosystem of packages with that as a dependency had to
// explicitly update to the new version.Let's break this down.
The Problem: Someone Made a Promise Go Never Made
Someone created a package called go4.org/unsafe/assume-no-moving-gc. The idea was clever: let programs officially declare "I know Go's garbage collector doesn't move objects around, and I'm relying on that."
Why would anyone care? Performance. If you know objects won't move, you can do clever tricks with memory pointers. Some high-performance libraries wanted this guarantee.
The catch? Go never promised this. The garbage collector happened to not move objects, but that could change in any future version.
The Hack That Broke Everything
Since Go never promised the GC wouldn't move objects, the assume-no-moving-gc authors built in a safety check. They used build constraints to explicitly whitelist each Go version they'd verified:
// go1.18.go
//go:build go1.18 && !go1.19
// go1.19.go
//go:build go1.19 && !go1.20
// unsupported.go - fails compilation on unknown versions
//go:build !go1.18 && !go1.19 && !go1.20
#error "This Go version hasn't been verified yet"The logic: "We've checked Go 1.18, 1.19, and 1.20 - their GCs don't move objects. But Go 1.21? We haven't verified it. Better fail loudly than let you run unsafe code."
Smart in theory. Disaster in practice.
Every time Go released a new version:
- The package intentionally stopped compiling (safety check triggered)
- Maintainers had to verify the new version's GC behavior, then add support
- Everyone using it had to wait, then update their dependencies
- Repeat every ~6 months when the next Go version dropped
The Cascade Effect
If it was just one obscure package, who cares? But this package had transitive dependencies.
Your Package
└── depends on inet.af/netaddr (popular IP address library)
└── depends on go4.org/intern
└── depends on assume-no-moving-gc 💥
Popular packages used it. Which meant their users were affected. Which meant their users were affected. One broken package at the bottom → thousands of broken builds at the top.
The Go team called it "significant friction" across the entire ecosystem. Every. Single. Release.
The Pragmatic Solution
The Go team had options:
- Ignore it - "Not our problem, don't use unsupported hacks"
- Block it - Make it impossible to do these tricks
- Fix it - Give them a stable way to do the "wrong" thing
They chose option 3.
Enter //go:linkname
Go has a special directive called //go:linkname. It's like a secret backdoor that lets external code access internal, private functions. It's powerful, dangerous, and explicitly unsupported.
//go:linkname heapObjectsCanMove
func heapObjectsCanMove() bool {
return false
}That //go:linkname comment above the function is Go saying: "Fine. You want to know if heap objects can move? Here's a function you can link to. It returns false. For now."
Now assume-no-moving-gc can link directly to this function instead of playing whack-a-mole with build tags. No more breakage every release. The ecosystem stabilized.
But Here's the Kicker
The Go team left themselves a nuclear option. Right there in the comment:
"If the Go garbage collector ever does move heap objects, we can set this to
trueto break all the programs using assume-no-moving-gc."
Read that again. They're not hiding it. They're not being subtle. They're explicitly saying: "We added this function so we can break you intentionally when we need to."
It's a killswitch. A tripwire. A boolean that, if flipped, will instantly tell thousands of packages "your assumptions are no longer valid."
Why This Matters (Even If You Don't Use Go)
1. Your "Internal" APIs Aren't Internal If They're Useful Enough
The Go team never intended for anyone to care whether heap objects move. But someone did. And they built on that assumption. And others built on them.
The lesson: If your code does something useful, someone will depend on it - even the parts you never meant to expose.
2. Breaking Changes Cascade Faster Than You Think
One package's clever hack became thousands of packages' build failure. Dependency trees amplify everything.
The lesson: Before you add a dependency, understand what it depends on. That chain matters.
3. Sometimes the Right Move Is Giving Users a Stable Way to Do the "Wrong" Thing
The Go team could have fought this. Instead, they said "fine, here's a stable target for your unstable behavior."
The lesson: Harm reduction beats prohibition when you can't actually stop the behavior.
4. Document Your Escape Hatches
That comment isn't just documentation - it's a warning. Everyone who uses heapObjectsCanMove knows exactly what they're signing up for.
The lesson: When you make tradeoffs, make them visible. Future maintainers (including you) will thank you.
What's the most interesting comment you've found in an open source codebase? I'd love to hear about it.