Hyrum’s Law in Golang

Go’s strict commitment to backward compatibility collides with Hyrum’s Law, which observes that any observable behavior in a widely used API will eventually be relied upon—right down to exact error message strings. Commenters trade examples from Go’s standard library, Windows, and network protocols to show how “stringly typed” errors, undocumented quirks, and even performance characteristics become de facto contracts that are hard to change later. Many argue that better API design (typed/sentinel errors, deliberate randomness, clear deprecation paths) can mitigate the problem, but accept that some accidental behaviors will still have to be supported indefinitely.

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.Rand output, 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 error with that message; checking the string was effectively the only way. A dedicated MaxBytesError type 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.