10

I want to express a function that can take any slice. I thought that I could do this:

func myFunc(list []interface{}) {
  for _, i := range list {
    ...
    some_other_fun(i)
    ...
  }
}

where some_other_fun(..) itself takes an interface{} type. However, this doesn't work because you can't pass []DEFINITE_TYPE as []interface{}. See: https://golang.org/doc/faq#convert_slice_of_interface which notes that the representation of an []interface{} is different. This answer sums up why but with respect to pointers to interfaces instead of slices of interfaces, but the reason is the same: Why can't I assign a *Struct to an *Interface?.

The suggestion provided at the golang.org link above suggests rebuilding a new interface slice from the DEFINITE_TYPE slice. However, this is not practical to do everywhere in the code that I want to call this function (This function is itself meant to abbreviate only 9 lines of code, but those 9 lines appear quite frequently in our code).

In every case that I want to invoke the function I would be passing a []*DEFINITE_TYPE which I at first thought would be easier to abstract until, again, I discovered Why can't I assign a *Struct to an *Interface? (also linked above).

Further, everytime I want to invoke the function it is with a different DEFINITE_TYPE so implementing n examples for the n types would not save me any lines of code or make my code any clearer (quite the contrary!).

It is frustrating that I can't do this since the 9 lines are idiomatic in our code and a mistype could easily introduce a bug. I'm really missing generics. Is there really no way to do this?!!

Community
  • 1
  • 1
Josh
  • 323
  • 4
  • 8

4 Answers4

9

In the case you provided, you would have to create your slice as a slice of interface e.g. s := []interface{}{}. At which point you could literally put any type you wanted into the slice (even mixing types). But then you would have to do all sorts of type assertions and everything gets really nasty.

Another technique that is commonly used by unmarshalers is a definition like this:

func myFunc(list interface{})

Because a slice fits an interface, you can indeed pass a regular slice into this. You would still need to do some validation and type assertions in myFunc, but you would be doing single assertions on the entire list type, instead of having to worry about a list that could possibly contain mixed types.

Either way, due to being a statically typed language, you eventually have to know the type that is passed in via assertions. It's just the way things are. In your case, I would probably use the func signature as above, then use a type switch to handle the different cases. See this document https://newfivefour.com/golang-interface-type-assertions-switch.html

So, something like this:

func myFunc(list interface{}) {
    switch v := list.(type) {
        case []string:
            // do string thing
        case []int32, []int64:
            // do int thing
        case []SomeCustomType:
            // do SomeCustomType thing
        default:
            fmt.Println("unknown")
    }
}
RayfenWindspear
  • 6,116
  • 1
  • 30
  • 42
4

No there is no easy way to deal with it. Many people miss generics in Go.

Maybe you can get inspired by sort.Sort function and sort.Interface to find a reasonable solution that would not require copying slices.

Grzegorz Żur
  • 47,257
  • 14
  • 109
  • 105
3

Probably the best thing to do is to define an interface that encapsulates what myFunc needs to do with the slice (i.e., in your example, get the nth element). Then the argument to the function is that interface type and you define the interface method(s) for each type you want to pass to the function.

You can also do it with the reflect package, but that's probably not a great idea since it will panic if you pass something other than a slice (or array or string).

func myFunc(list interface{}) {
    listVal := reflect.ValueOf(list)
    for i := 0; i < listVal.Len(); i++ {
        //...
        some_other_fun(listVal.Index(i).Interface())
        //...
    }
}

See https://play.golang.org/p/TyzT3lBEjB.

Andy Schweig
  • 6,597
  • 2
  • 16
  • 22
1

Now with Go 1.18+, you can use the generics feature to do that:

func myFunc[T any](list []T) {
  for _, item := range list {
    doSomething(item)
  }
}
Amit Itzkovitch
  • 175
  • 1
  • 7