Golang nil returns suck ლ(ಠ益ಠლ)


Why might you return nil

In many languages, if you want to represent “nothing” is returned, a common pattern is to use nil as a return value. A common alternative to this is the NullObjectPattern, since a 0/nil/null value generally does not respond to the same number of messages as a legitimate object value. This far more sparse interface is why languages like Ruby have introduced a Safe Navigation Operator. In languages that expose memory directly or indirectly like C++, trying to call object methods on a pointer to NULL could cause mangling of memory offset from that “zero” address.

What happens in Golang with a nil return

golang assertions against nil for multiple different representations
Fighting through trying to match a golang nil return value (GitHub code link)

If you have a reference or pointer return type, you can specify nil as a return value from it:

type foo struct {
}

func fooMeOncePointerWithNil() *foo {
	return nil
}

So if you’re writing a test for this (trivial) function, you might be tempted to write

func TestFooMeOncePointerToArrayWithNil(t *testing.T) {
	assert.Equal(t, nil, fooMeOncePointerToArrayWithNil(), "Test did not return nil")
}

But your assertion will fail, because the return value is *not* nil:

❯ go test
--- FAIL: TestFooMeOncePointerToArrayWithNil (0.00s)
    main_test.go:23:
        	Error Trace:	/Users/tpowell/projects/golang/nilTest/main_test.go:23
        	Error:      	Not equal:
        	            	expected: <nil>(<nil>)
        	            	actual  : *[]testy.foo((*[]testy.foo)(nil))
        	Test:       	TestFooMeOncePointerToArrayWithNil
        	Messages:   	Test did not return nil
FAIL
exit status 1
FAIL	testy	0.104s

The actual type is nil cast to a *[]testy.foo cast to a *[]testy.foo.

You can see this with returning to a pointer to an array of strings more clearly:

func stringPointerToArrayNil() *[]string {
	return nil
}

func TestStringNil(t *testing.T) {
	assert.Equal(t, nil, stringPointerToArrayNil(), "Function did not return nil")
}

Which fails similarly, although slightly more readably:

--- FAIL: TestStringNil (0.00s)
    main_test.go:42:
        	Error Trace:	/Users/tpowell/projects/golang/nilTest/main_test.go:42
        	Error:      	Not equal:
        	            	expected: <nil>(<nil>)
        	            	actual  : *[]string((*[]string)(nil))
        	Test:       	TestStringNil
        	Messages:   	Function did not return nil
FAIL
exit status 1
FAIL	testy	0.246s

What Technically Works?

So how we make a nil return value match? Well, you can typecast nil explicitly to match the implicit casting of the returned nil:

func TestPassFooMeOncePointerToArrayWithNil(t *testing.T) {
	assert.Equal(t, (*[]foo)(nil), fooMeOncePointerToArrayWithNil(), "Test did not return nil")
}

Now you’ll get:

❯ go test
PASS
ok testy 0.141s

A better “nil”?

Having to contort a language to do a basic thing is usually an indicator you’re probably using it wrong. In this case, perhaps you just want an empty array:

func fooMeOnceArray() []foo {
	return []foo{}
}

Now you can validate this with the len function on the returned array:

func TestFooMeOnceArray(t *testing.T) {
	assert.Equal(t, 0, len(fooMeOnceArray()), "Test did not return nil")
}

This, of course, passes, but tested against the number of items returned instead of checking for an arbitrary nil value.

Maybe you just want to check error

Golang supports multiple return values, and you can add a return of an error type:

func fooMeOnceArrayError() ([]foo, error) {
	return []foo{}, nil
}

The error type *can* be cleanly compared to nil:

func TestFooMeOnceArrayError(t *testing.T) {
	_, error := fooMeOnceArrayError()
	assert.Equal(t, nil, error, "Test did not return nil")
}

This comparison to nil passes:

❯ go test
PASS
ok testy 0.238s

Conclusion

The bigger lesson in all of this is that if the direction that you’re going down in a language feels unnatural, there might be a better way to do things. Or you might not even be using a pattern that is preferred for the language. Take a step back and look at the bigger picture.


Leave a Reply

%d bloggers like this: