Hyrum’s Law in Golang
Historical compatibility anecdotes
- Windows 3.x vs. SimCity: game had a use‑after‑free bug that happened to “work” on Windows 3.x; Windows 95 added a special allocator mode triggered only for SimCity (and similar apps) to preserve behavior.
- This is cited as a classic Hyrum’s Law case: undocumented quirks become de‑facto API that must be preserved for compatibility.
Go’s compatibility culture
- Go maintainers emphasize very strong backward compatibility; even subtle behaviors (e.g.,
rand.Randoutput, crypto key generation details, map iteration order) are treated as part of the contract once widely used. - They sometimes add deliberate behavior (e.g., reading an extra random byte, randomized map iteration) to prevent future dependence on specific internals.
- GODEBUG flags are used to roll out behavior changes more safely and measure impact.
Stringly errors vs. typed errors
- Core example: a specific HTTP error string (“request body too large”) cannot be changed because many projects compare the string.
- Earlier Go versions only exposed this as a plain
errorwith that message; checking the string was effectively the only way. A dedicatedMaxBytesErrortype came much later. - Some argue this is misuse by consumers and shouldn’t be supported; others say the stdlib’s original string‑only design forced this, so blame lies with the API.
Debate: who should adapt?
- One side: code that parses error strings is “just bad” and should be allowed to break; developers must accept risk when depending on undocumented details.
- Other side: real‑world constraints often force hacks (“ship now, fix later”), and Go’s promise is precisely that such working code will keep working; otherwise software is never “finished.”
- Some would tolerate more breaking changes to enable improvements; others say Go’s stability is its core value and would switch languages if guarantees were relaxed.
Fighting Hyrum’s Law
- Suggested techniques:
- “Greasing”/randomization (e.g., QUIC/TLS unused fields, randomized hashes/map iteration) so consumers cannot safely rely on specifics.
- Avoid being “liberal in what you accept” (Postel’s Law) to prevent ossification.
- Static analysis or lint rules to flag string comparisons on errors.
APIs, semver, and emergent dependencies
- Many note that semver can’t fully solve Hyrum’s Law: you often don’t know what people rely on, so you won’t bump a major version.
- Tests are a frequent place where unintended dependencies surface: order of maps/sets, exact error messages, timezones, gzip bytes, and performance characteristics.