Hyrum's Law in Golang

Abenezer Belachew

Abenezer Belachew · November 09, 2024

4 min read

I recently stumbled upon an intriguing comment while exploring the Go codebase.

"Due to Hyrum's law, this text cannot be changed."

func (e *MaxBytesError) Error() string {
	// Due to Hyrum's law, this text cannot be changed.
	return "http: request body too large"
}
  • Prior to this, I had never heard of Hyrum's law.
  • A quick search revealed that it is a principle named after Hyrum Wright, a software engineer at Google.

The "law" is simple:

With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody.

In other words, any behavior that can be observed in code — whether intentional or accidental — will eventually become something that someone, somewhere, relies on.

So in the code above, the author is acknowledging that the error message cannot be changed because it is likely being relied upon by someone, somewhere. Even though it might seem trivial to tweak the error message, doing so could cause unintended issues for anyone relying on this specific message. In this case, a seemingly small change could break existing code where someone depends on the exact phrasing of "http: request body too large".

For example, here are some open source codebases that will be affected if the error message is changed:

This wasn't the only instance. I found similar comments referencing Hyrum's Law in Go's crypto/rsa and internal/weak packages as well.

Here they are:

crypto/rsa/rsa.go

func EncryptOAEP(hash hash.Hash, random io.Reader, pub *PublicKey, msg []byte, label []byte) ([]byte, error) {
	// Note that while we don't commit to deterministic execution with respect
	// to the random stream, we also don't apply MaybeReadByte, so per Hyrum's
	// Law it's probably relied upon by some. It's a tolerable promise because a
	// well-specified number of random bytes is included in the ciphertext, in a
	// well-specified way.

crypto/rsa/pss.go

func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte, opts *PSSOptions) ([]byte, error) {
	// Note that while we don't commit to deterministic execution with respect
	// to the rand stream, we also don't apply MaybeReadByte, so per Hyrum's Law
	// it's probably relied upon by some. It's a tolerable promise because a
	// well-specified number of random bytes is included in the signature, in a
	// well-specified way.

	if opts != nil && opts.Hash != 0 {
		hash = opts.Hash
	}

internal/weak

Using go:linkname to access this package and the functions it references
is explicitly forbidden by the toolchain because the semantics of this
package have not gone through the proposal process. By exposing this
functionality, we risk locking in the existing semantics due to Hyrum's Law.

Observation

This is obviously not specific to Golang and is mentioned in other codebases as well.

TBH, this whole thing reminds me of how JavaScript's evolution over the years has been heavily shaped by widespread reliance on all sorts of quirky, unintended behaviors, which became de facto standards. Now, I finally know what to refer to this phenomenon as — Hyrum's Law.

Final thoughts

  • A good reminder to be careful when changing code others might depend on—and to try designing things in a way that doesn't accidentally lock in weird quirks.
  • And even better, to design systems in a way that minimizes the chances of unintended behaviors being relied upon in the first place.
  • After all, you know the saying: "All it takes is one small change to... something something..." I don't remember the whole quote.

😶‍🌫️️