Go is weird: When is a Go slice empty?
Let a
be a Go slice. Under what conditions is the following
statement true: len(a) == 0
?
Unsurprisingly, when the slice is empty, its length is zero:
a := []int{}
len(a) == 0
But, possibly unexpectedly, the length of a slice is zero even
when the slice is nil
:
var a []int
len(a) == 0
On some level, this makes sense:
- What else should
len(a)
return for anil
slice? - Having
len(a)
fail (e.g. panic) for anil
slice seems wrong. - Returning a length and an error is also bad.
Yet, having len(nil) == 0
can be dicey. Imagine a function that
returns a slice of matching elements — for example, resulting
from a database query. Its signature is surely:
func findElements() ( []int, error )
The error
is only not nil
if there is a database error, or a
comparable condition. But what should the function return if
it didn’t find any elements? There are two options:
return nil, nil
or
return []int{}, nil
One can argue that returning an empty (not nil
) slice is
correct: the set of matching elements is empty; not nil
.
How should the calling code check whether any elements were found? Again, there are two options:
res, err = findElements()
if res == nil { ... }
or
res, err = findElements()
if len(res) == 0 { ... }
I’d argue that, semantically, the second form is correct:
after all, the result set is empty, not invalid. Note that
Go’s convention of having len(nil) == 0
lets the second
form of the conditional be semantically correct, either way.
But the first form of the conditional is a problem, because
it will only be true if findElements()
returns nil
, not
when it returns an empty slice.
It took me a full day to track down a problem that resulted from this difference…
As an aside: if we don’t need to check whether any elements are found, but merely need to process each, then either return value is fine, because we can range over a nil slice, as well as over an empty slice.
More on nil slices
Turns out, nil
slices in Go behave a lot like empty slices.
Let a
be a nil
slice. Then, the following operations are
allowed:
- Take the length:
len(a) == 0
- Append:
a = append(a, v)
- Range over it:
for i, b := range a { ... }
The only regard in which a nil
slice is different from an
empty slice is that a == nil
is only true for nil
slice,
but false for an empty slice.
Given this behavior, I regard nil
slices as a bit of a
mis-feature: they don’t seem necessary, yet they do introduce
a certain amount of ambiguity and uncertainty, as discussed
above.
I have seen advice suggesting that a function such as
findElements()
should return nil
in place of an empty
result set, with the justification that this is “more
idiomatic Go”. That strikes me as a case of “if you can’t
fix it, feature it” — just because the language
provides nil
values, it is idiomatic to use them? I
don’t think so. An empty result set is just that: empty.
Not nil
.
More on nil
In fact, I wonder whether having nil
at all is a mistake.
I have not thought about this deeply, but the only reason
that mandates the existence of a nil
value seems to be
as default value for pointer types. I never understood why
Go brought back “pointers”, which can point anywhere
(including nil
) as opposed to “references”, which must
refer to a legal value at all times. (In particular since
Go’s pointers aren’t even pointers, but references: you
can’t do pointer arithmetic on them.)
Not that these thoughts are new: Tony Hoare famously called the invention of Null References: The Billion Dollar Mistake.
An Internet search on that headline will turn up quite a few discussions on the topic, but no real resolution, either.
I do admit that signaling “missing” data is frequently necessary (just
look at database NULLs), and nobody seems to have really found a fully
satisfactory solution. I have not worked with languages that natively
support Option Types, but
they remind me awfully of Go’s (dreaded) idiom of returning a value
and an error (with the value only being valid if the error is nil
).
I’d just like to see the use of “missing” data reduced as much as
possible, and made explicit, whenever it occurs.