La Vita è Bella

2020-09-04

Typed nil in Go

In Go, nil, one of the "magic" literals, could actually have types. Although in most cases this won't bite you, in rare cases it does bite and causes bugs. I was bitten by it for the second time (as far as I can remember) earlier this week.

This was the commit that introduced the bug and this was the commit that fixed it. The key issue is on these 2 lines:

41   var r intner = args.R
42   if r == nil {

What does this code do?

args.R is an optional argument to the function. The lines here are trying to do the fallback to the default implementation when it's absent.

Why does the old code not work?

On L42, when doing the comparison between r and nil, nil is actually a typed nil to match the type of r. Since r has the type of intner, this line actually implies:

if r == intner(nil) {

The problem is that args.R actually have a different type: *math/rand.Rand. When it's nil, that's actually typed nil of (*math/rand.Rand)(nil). When assigning it into r on L41, the type of the typed nil doesn't change, despite that r has a different type (the assignment is allowed because *math/rand.Rand is a concrete type that implements intner interface, so it's "assignable" in Go's type system). As a result, the comparison is actually:

if (*math/rand.Rand)(nil) == intner(nil) {

Since they are different types of typed nils, the comparison would never be true.

Why does the fix work?

In the fix, we changed to compare args.R against nil directly:

42   if args.R != nil {

So it always have the correct type for the typed nil.

09:32:40 by fishy - Permanent Link

May the Force be with you. RAmen