3

I have a function removeFrom that removes an item from a slice. It accepts a float64 slice and an index:

func removeFrom(slice []float64, index int) []float64 {
    if len(slice) > index {
        return append(slice[:index], slice[index+1:]...)
    }
}

It works fine, but now I have to remove from slices of integers also. So how could I change this to accept both types (and return a slice of the given type)? I tried to use empty interfaces but apparently I have to do some conversion inside the function and I didn't find out how to do it.

blackgreen
  • 34,072
  • 23
  • 111
  • 129
abel
  • 133
  • 7

2 Answers2

5

Short answer? you can't.

Long answer, you still can't directly do it, BUT:

func removeFrom(slice interface{}, index int) interface{} {
    switch slice := slice.(type) {
    case []float64:
        if len(slice) > index {
            return append(slice[:index], slice[index+1:]...)
        }
    case []int64:
        if len(slice) > index {
            return append(slice[:index], slice[index+1:]...)
        }
    case []int:
        if len(slice) > index {
            return append(slice[:index], slice[index+1:]...)
        }
    default:
        log.Panicf("unknown type: %T", slice)
    }
}
OneOfOne
  • 95,033
  • 20
  • 184
  • 185
4

Go doesn't support generics, there is no "common ancestor" for all slice types ([]interface{} is not "compatible" with []int for example, see Cannot convert []string to []interface {} for more details).

So if you want your function to accept any slice types, you have to use interface{} (both for the "incoming" parameter and for the return type). But now you have an (interface) wrapper value to which you can't apply slicing and which you can't pass to the builtin append() function.

You could use type assertion and type switches for known types, but you would have to repeat code for each, so it's not really a step ahead.

Actually there's a way to create a removeFrom() function that will work for all slice types, with using reflection.

reflect.Value is a type describing any Go value. It has supporting methods for different types of Go values, including slices.

What's interesting to us is the Value.Slice() method:

func (v Value) Slice(i, j int) Value

We can use it to slice a slice. Good. It is a key point in our element-removal algorithm. What's still needed is to "join" 2 slices, the one before and the one after the removable element. Luckily the reflect package also has support for this: reflect.AppendSlice():

func AppendSlice(s, t Value) Value

As the last remaining key, we can use Value.Len() to get the length of any slice.

We now have everything that's needed for our general removeFrom() function, which is surprisingly simple:

func removeFrom(s interface{}, idx int) interface{} {
    if v := reflect.ValueOf(s); v.Len() > idx {
        return reflect.AppendSlice(v.Slice(0, idx), v.Slice(idx+1, v.Len())).Interface()
    }
    return s
}

Really, that's all. Testing it:

for i := 0; i < 4; i++ {
    fmt.Println(removeFrom([]int{0, 1, 2}, i), "missing:", i)
}
for i := 0; i < 4; i++ {
    fmt.Println(removeFrom([]string{"zero", "one", "two"}, i), "missing:", i)
}

Output (try it on the Go Playground):

[1 2] missing: 0
[0 2] missing: 1
[0 1] missing: 2
[0 1 2] missing: 3
[one two] missing: 0
[zero two] missing: 1
[zero one] missing: 2
[zero one two] missing: 3

Notes:

This solution uses reflection, so it will be slower than another solution not using reflection but having the concrete, supported types "wired in". Quick benchmarks show this general solution is 2.5 times slower than a non-reflection with wired-in types. Should be weighted whether performance or convenience/general solution is more important. Or you may combine this with concrete types: you may add a type switch to handle frequent types, and only revert to this general solution if the actual concrete type is not handled by the type switch.

icza
  • 389,944
  • 63
  • 907
  • 827
  • Note that this horribly slow compared to type assertion. – OneOfOne May 22 '16 at 21:59
  • @OneOfOne This general solution uses reflection, so yes, it will be slower than something that has concrete types "wired in". "Horribly" is a strong word though, as by that I would expect the difference being order of magnitude(s). Quick benchmarks show the general, reflection solution is 2.5 times slower. – icza May 22 '16 at 22:20
  • I can't edit it, but yeah horribly is a strong word, my apologizes. – OneOfOne May 22 '16 at 22:22