Extend capacity of slice wrapped in interface type using reflection

55 Views Asked by At

I want to extend the capacity of a slice using reflection, but the variable itself is an interface{} and I need to get the underlying type first. However, trying to access the underlying type results in having an unaddressable value that I can't extend the capacity of. I know I need to pass in a pointer to the slice, because otherwise the unaddressable value won't bubble my changes up, but if I pass a pointer to reflect.ValueOf, I get a Pointer->Interface->Slice situation, whereas I want Pointer->Slice, i.e. an addressable version of the slice.

I've got a minimal example thrown together to demonstrate the problem. The extend function receives a variable of type any that is actually a slice (could be a slice of any type, e.g. []any, []string, []MyStruct). In my particular situation I can't use generics, this example is just a boilt down version of the preconditions that led here.

In my example I have the things I have tried listed, also available online: https://go.dev/play/p/LBOrORw3oSs

package main

import (
    "fmt"
    "reflect"
)

func extend(v any) (any, error) {
    // however, on array set, we can test if the value is maybe an array:
    rv := reflect.ValueOf(v)
    rt := rv.Type()
    kind := rt.Kind()
    if kind != reflect.Slice {
        return nil, fmt.Errorf("value is not a slice, but rather %T", v)
    }
    c := rv.Cap()
    c = c*2 + 1
    // this panics because rv is not addressable
    rv.SetCap(c)
    // in consequence, this panics as well, because rv is not addressable
    rv.Addr().SetCap(c)
    // I tried getting a pointer to the value
    ptr := &v
    rvPtr := reflect.ValueOf(ptr)
    rvPtr.SetCap(c)        // panics because kind is Pointer
    rvPtr.Elem().SetCap(c) // panics because kind is Interface
    // calling elem twice gives me the correct kind (slice) because now I got the underlying type,
    // but now we have the same situation as before: unaddressable value
    rvPtr.Elem().Elem().SetCap(c)

    // have to return here because we extend the capacity of a slice that is not passed as pointer
    return rv.Interface(), nil
}

func main() {
    slice := []any{}
    result, err := extend(slice)
    fmt.Println(err)
    fmt.Println(cap(result.([]any)))
}

1

There are 1 best solutions below

2
JimB On BEST ANSWER

If the type is not known until runtime and you can't use slices.Grow, you can perform the same operation using the reflect package.

The method for extending the capacity of a slice using make and copy directly translates to functions available through reflect. You allocate a new slice of the correct type, length and capacity, then you copy the new elements into that slice.

func extend(v any) any {
    rv := reflect.ValueOf(v)
    extended := reflect.MakeSlice(rv.Type(), rv.Len(), rv.Cap()*2+1)
    reflect.Copy(extended, rv)
    return extended.Interface()
}