n.p
n.p

Reputation: 31

return slice of empty interface from function

Can someone explain why this doesn't work and how can we return slices of interfaces, []interface{}, from functions like showed in the example?

import (
    "fmt"
)

func main() {
    var test []string
    Test(&test)
    fmt.Println(test)
}

func Test(t interface{}) {
    a := []interface{}{"first", "second"}
    fmt.Println(a)
    t = a
}

Example of running code can be found here: https://play.golang.org/p/vcEGHSdWrjv

BTW, this is the func I'm trying to extract data from: https://godoc.org/github.com/mongodb/mongo-go-driver/mongo#Collection.Distinct

Note: the type we expect is not always of type []string, I'm just using string as an example here.

Thanks!

Upvotes: 0

Views: 2422

Answers (2)

Thundercat
Thundercat

Reputation: 121149

Because an []string and a []interface{} are different types, you cannot assign one to the other.

You must copy the slice to convert []interface{} to a slice of some specific type. If you know that the []interface{} always contains string values, then use the following:

func stringSlice() []string {
    a := []interface{}{"first", "second"} // query result
    fmt.Println(a)

    result := make([]string, len(a))
    for i := range a {
       var ok bool
       result[i], ok = a[i].(string)
       if !ok {
          // handle error with unexpected type
       }
    }
    return result
}

If the result can have arbitrary element types, then use reflection to copy the slice:

func anySlice(result interface{}) {
    a := []interface{}{"first", "second"} // query result

    slice := reflect.ValueOf(result).Elem()
    elementType := slice.Type().Elem()
    for _, v := range a {
        rv := reflect.ValueOf(v)
        if !rv.Type().AssignableTo(elementType) {
            // handle error with unexpected type
        }
        slice.Set(reflect.Append(slice, rv))
    }
}

Use it like this:

var s []string
anySlice(&s)

playground example

Upvotes: 1

eugenioy
eugenioy

Reputation: 12403

In you example, you are not trying to "return" a slice but rather you seem to be looking to modify the argument to point to a new slice of strings.

The way you are doing it does not work cause in Go, arguments are passed by value.

When you do this:

t = a

t is a copy of the &test you are sending to the function as an argument.

So modifying t does not change your test variable.

You need to pass in the address of a pointer in order to be able to modify what the pointer points to.

Try this way:

func main() {
    var test *[]string
    Test(&test)
    fmt.Println(*test)
}

func Test(t interface{}) {
    a := []string{"first", "second"}
    fmt.Println(a)
    *t.(**[]string) = &a
}

Output:

[first second]
[first second]

Playground: https://play.golang.org/p/tliMrmliykp

Upvotes: 2

Related Questions