A sneaky Golang bug
Wed 02 August 2023 — download

Today at work, I needed a function in Go to remove duplicates from a slice, and thus wrote something like this using the generic-based slices package:

func removeDuplicates(s []mytype) []mytype {
    slices.SortFunc(s, less)
    slices.CompactFunc(s, eq)
    return s
}

Can you spot the bug? Here are the prototypes of the two functions:

func SortFunc[E any](x []E, less func(a, b E) bool)
func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S

The first has no return value, while the second does, unused in our case, hence the bug. It's interesting to note that the go compiler is perfectly happy with this, and doesn't issue any warning: it was extraordinarily fun to pinpoint.

I reached out to Ian Lance Taylor who implemented those functions in 2021 and he pointed me to Go Slices: usage and internals . Things indeed do become obvious once looking at the implementation of slice:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

Both slices.SortFunc and slices.CompactFunc are taking a slice as parameter, and not a pointer to a slice, meaning that any changes to len and cap will be local to the function.

Anyway, There is a proposal to require return values to be explicitly used or ignored open since 2017, but it didn't go anywhere for now. There is also another proposal to make go vet better at highlighting error mishandling, as well as errcheck, but those wouldn't really help in this case.