meesterguyperson
meesterguyperson

Reputation: 1884

How would you access the underlying array passed to a function expecting an empty interface in Go?

So let's say that we have a function of the following form:

func WorkMagic(obj interface{}) interface{} {
    switch t := obj.(type) { 
    case string:
        // Do string magic
    default:
        // Do slice magic
    }
    ...
}

I am expecting obj to be either a string or a slice, which I can ascertain via the switch. In the case of a slice, I want to be able to do ordering work on any arbitrary slice, regardless of type. Seems like the best way to accomplish this is using the unsafe package in a similar fashion to that discussed in this article.

Here however, the function accepts a specific type of slice ([]string), whereas I would like to be able to work on any slice. So the question is, given that I am accepting an empty interface as input, how might I access the underlying slice / array using unsafe.Pointer so as to be able to loop through and modify which value is associate with which index?

Upvotes: 1

Views: 176

Answers (1)

thwd
thwd

Reputation: 24818

You'll want to use reflection. It enables you to work generically without giving up type and memory safety like unsafe would. Read the Go blog's Laws of Reflection.

func actOnSlices(i interface{}) {

    v := reflect.ValueOf(i)

    for v.Kind() == reflect.Ptr { // dereference pointers
        v = v.Elem()
    }

    if v.Kind() != reflect.Slice { // ensure you actually got a slice
        panic("given argument is not a slice")
    }

    // do slice stuff

}

Edit to answer your second question:

Yes – this can be done: elements of a slice are adressable and hence settable. See the following working example:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    s := []string{"foo", "bar"}
    fmt.Println(swapIndexes(s, 0, 1)) // prints [bar foo]
}

func swapIndexes(i interface{}, x, y int) interface{} {

    v := reflect.ValueOf(i)

    for v.Kind() == reflect.Ptr { // dereference pointers
        v = v.Elem()
    }

    if v.Kind() != reflect.Slice { // ensure you actually got a slice
        panic("given argument is not a slice")
    }

    t := v.Index(x).Interface()

    v.Index(x).Set(v.Index(y))

    v.Index(y).Set(reflect.ValueOf(t))

    return v.Interface()

}

Edit to answer your third question:

The unsafe package is not something you'll encounter much in user-land code. It exists to implement certain features (e.g. reflection, C interaction) that need to circumvent Go's safety guarantees to work. Using unsafe is unsafe, as the name suggests, because you can mess up big time without even realizing. By using unsafe, you're incurring in a big trade-off, so it better be worth it. Quoting @twotwotwo:

The downside of unsafe is that if you mess up you're in the old days of segfaults, memory corruption, and buffer-overflow security holes.

Also, as @twotwotwo suggested; it's more "Go-like" to repeat code than using reflection to achieve genericity.

To Go's type-system, []string and []int are two completely separate and unrelated types. just as int and string would be. The relation (both are slices) is obvious only to the programmer. There is no way of expressing "a slice" without saying a slice of what.

Upvotes: 2

Related Questions