September 10th, 2024

The Joy of Reading Source Code

This morning I was drinking my coffee and eating a pastry while reading about how you shouldn’t defer Close() on writable files and dove into the HN comments. The first comment calls out a minor improvement that can be made to the code. Specifically, using errors.Join to combine the returned errors in a succinct and clear way. I wasn’t familiar with errors.Join so I decided to dive into the source code a bit.

A bit of an aside, but I use Dash mapped to opt+<space> to open documentation quickly, and found errors.Join. This took me right to the Go documentation, which has this excellent feature where clicking the method name takes you right to the source code. This made it really easy to dive into the documentation and source of errors.Join at a whim.

What I learned from errors.Join

The errors.Join source is pretty simple, but it has a few interesting things going on. Overall the code is short and direct, but the joinError struct and associated methods had me asking a few questions.

The first question was almost immediately “how does this work with Unwrap?” and the answer is that it doesn’t. The documentation for Unwrap actually calls this out too, since the signature is Unwrap() []error and not Unwrap() error. This makes sense to me, since handling slices of error would make Unwrap significantly more complex and have some less-than-desirable performance implications. Despite the API gap, I think it’s the correct design decision since there’s likely no need to optimize for edge cases like this one.

It would also be simple to handle a top-level Join unwrap if needed too. e.g.

type joinUnwrapper interface {
	Unwrap() []error
}
// AnyIs supports checking if any error in the provided err contains
// the target error. It differs from `errors.Is` by supporting `errors.Join` errors.
func AnyIs(err error, target error) bool {
	if unwrappable, ok := err.(joinWrapper); ok {
		for _, e := range unwrappable.Unwrap() {
			if errors.Is(e, target) {
				return true
			}
		}
	}

	return errors.Is(err, target)
}

After writing this code, I think it might be a bit of a smell. If I controlled the code that emits the errors.Join error I’d likely update it to better encapsulate certain error conditions to avoid the need to loop over errors in this way.

The second question I asked was more around API ergonomics. How does errors.Join represent the Error() string value? It’s formatted how I’d expect with each error having its own new line, but it is doing something interesting under the hood. Instead of using a strings.Builder or casting bytes to a string like I’d have (potentially naively) expected, it’s using unsafe.String. It’s a micro-optimization to avoid extra allocations you’d get from strings.Builder or string(b) that I haven’t had to reach for yet, but now I know about it and have another (sharp) tool in my toolbox.

That was effectively where the source code ended and I had finished my pastry, so I decided to get back to work after this brief respite.

It’s a lot of fun diving into the source code of projects you use day-to-day and this was no different. In this case even though it was a quick read of a well scoped API I learned a thing or two. I got to see how the authors approached implementing the API under-the-hood which was different than how I imagined it, and I also got to learn about unsafe.String and how it’s used in production.