Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Express function that takes any slice

Tags:

types

go

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?!!

like image 579
Josh Avatar asked Mar 23 '17 21:03

Josh


People also ask

How do I return a slice in go?

In Go, there are two functions that can be used to return the length and capacity of a slice: len() function - returns the length of the slice (the number of elements in the slice) cap() function - returns the capacity of the slice (the number of elements the slice can grow or shrink to)

What is make () In Golang?

In Golang, make() is used for slices, maps, or channels. make() allocates memory on the heap and initializes and puts zero or empty strings into values. Unlike new() , make() returns the same type as its argument. Slice: The size determines the length.

Which of the following function returns the capacity of Slice has how many elements it can be accommodated?

C - cap function returns the capacity of slice as how many elements it can be accomodate. D - All of the above. Q 2 - Which of the following function returns the total number of elements present in a slice?


2 Answers

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.

like image 173
Andy Schweig Avatar answered Oct 27 '22 18:10

Andy Schweig


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")
    }
}
like image 36
RayfenWindspear Avatar answered Oct 27 '22 18:10

RayfenWindspear