gogo-reflect

In Go, how can I make a generic function with slices?


Let's say I want to write a function that finds a value in a slice

I intuitively want to write:

func find(s []interface{}, f func(interface{})bool) int {
    for i, item := range s {
        if f(item) {
            return i
        }
    }
    return -1
}

however I don't manage to do this with Go. I could have an interface with

Len() int
Value(int) interface{}
...

and this would work but in my real code things are more complicated (I need to do slices[from:end] etc), append, ... etc and if I redefine all this in an interface I end up having a lot of code. Is there a better way?


Solution

  • Seeing as all of the answers here were written before go added generics, I'll add how you would implement this in go1.18+

    Note, however, that as of go1.21, there is a slices package that contains many helpful, generic slice functions.

    This is the implementation of IndexFunc straight from the standard library

    // IndexFunc returns the first index i satisfying f(s[i]),
    // or -1 if none do.
    func IndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
        for i := range s {
            if f(s[i]) {
                return i
            }
        }
        return -1
    }
    

    Notice how the function uses the type parameters [S ~[]E, E any] instead of just S []any. The difference is due do how slices are typed in go. Just like how you can't assert a slice of one type to a slice of another even if it's possible to assert the elements (ex. []int{}.([]any) is invalid even though int satisfies any), you can't use an []int as a parameter of type S if S is constrained to []any.

    Instead, it uses S ~[]E to constrain S to be a slice with elements of type E, where E can be any type. Additionally, this allows functions to use the element type separately, which you can see with the second argument: f func(E) bool.