如何在Golang中通过引用传递带有值类型接口的地图切片

I want to pass []map[string]interface{} by reference to a function. Here is my attempt.

package main

import "fmt"

func main() {

    b := map[string]int{"foo": 1, "bar": 2}
    a := [...]map[string]int{b}

    fmt.Println(a)

    editit(a)

    fmt.Println(a)
}

func editit(a interface{}) {
    fmt.Println(a)

    b := map[string]int{"foo": 3, "bar": 4}
    a = [...]map[string]int{b}

    fmt.Println(a)
}

https://play.golang.org/p/9Bt15mvud1

Here is another attempt and what I want to do finally. This does not compile.

func (self BucketStats) GetSamples() {

    buckets := []make(map[string]interface{})
    self.GetAuthRequest(self.url, &buckets)

    //ProcessBuckets()
}

func (self BucketStats) GetAuthRequest(rurl string, **data interface{}) (err error) {
    client := &http.Client{}

    req, err := http.NewRequest("GET", rurl, nil)
    req.SetBasicAuth(self.un, self.pw)
    resp, err := client.Do(req)
    if err != nil {
            return
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
            return
    }

    // it's all for this!!!
    err = json.Unmarshal(body, data)

    return
}

The function is passing a **interface{} to Unmarshal. To pass the the *[]map[string]interface{} through to Unmarshal, change the function signature to:

 func (self BucketStats) GetAuthRequest(rurl string, data interface{}) (err error) {

Several things wrong here.

First, [...]map[string]int{b} is not in fact a slice, but a fixed-length array. The [...] syntax means "make an array, and set the length at compile time based on what's being put into it". The slice syntax is simply []map[string]int{b}. As a result, your call to editit(a) is in fact passing a copy of the array, not a reference (slices are innately references, arrays are not). When a is reassigned in editit(), you're reassigning the copy, not the original, so nothing changes.

Second, it's almost never useful to use pointers to interfaces. In fact, the Go runtime was changed a few versions back to not automatically dereference pointers to interfaces (like it does for pointers to almost everything else) to discourage this habit. Interfaces are innately references already, so there's little reason to make a pointer to one.

Third, you don't actually need to pass a reference to an interface here. You're trying to unmarshal into the fundamental data structure contained within that interface. You don't actually care about the interface itself. GetAuthRequest(rurl string, data interface{}) works just fine here.

func (self BucketStats) GetSamples() {

    var buckets []map[string]interface{}
    self.GetAuthRequest(self.url, &buckets)

    //ProcessBuckets()
}

func (self BucketStats) GetAuthRequest(rurl string, data interface{}) (err error) {
    client := &http.Client{}

    req, err := http.NewRequest("GET", rurl, nil)
    req.SetBasicAuth(self.un, self.pw)
    resp, err := client.Do(req)
    if err != nil {
            return
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
            return
    }

    // it's all for this!!!
    err = json.Unmarshal(body, data)

    return
}

Let me walk you through what exactly takes place, in order:

  • buckets := var buckets []map[string]interface{} We don't need a make here, because json.Unmarshal() will fill it for us.

  • self.GetAuthRequest(self.url, &buckets) This passes a reference into an interface field. Within GetAuthRequest, data is an interface with underlying type *[]map[string]interface{} and an underlying value equal to the address of the original buckets variable in GetSamples().

  • err = json.Unmarshal(body, data) This passes the interface data, by value, to the interface argument to json.Unmarshal(). Inside json.Unmarshal(), it has new interface v with underlying type *[]map[string]interface{} and an underlying value equal to the address of the original buckets variable in GetSamples(). This interface is a different variable, with a different address in memory, from the interface that held that same data in GetAuthRequest, but the data was copied over, and the data contains a reference to your slice, so you're still good.

  • json.Unmarshal() will, by reflection, fill the slice pointed to by the interface you handed it with the data in your request. It has a reference to the slice header buckets that you handed it, even though it passed through two interfaces to get there, so any changes it make will affect the original buckets variable.

When you get all the way back up to ProcessBuckets(), the buckets variable will contain all of your data.


As an ancillary suggestion, don't use named returns if your function is more than a few lines long. It's better to explicitly return your variables. This is particularly important because of variable shadowing. For example, in your GetAuthRequest() function, it will never return a non-nil error. This is because you're declaring an error variable err in the function signature, but you're immediately shadowing it with a local variable err using the short declaration in req, err := http.NewRequest("GET", rurl, nil). For the remainder of the function, err now refers to this new error variable rather than the one defined as the return variable. As a result, when you return, the original err variable in the return is always nil. A much better style would be:

func (self BucketStats) GetAuthRequest(rurl string, **data interface{}) error {
    client := &http.Client{}

    req, err := http.NewRequest("GET", rurl, nil)
    req.SetBasicAuth(self.un, self.pw)
    resp, err := client.Do(req)
    if err != nil {
            return err
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
            return err
    }

    // it's all for this!!!
    return json.Unmarshal(body, data)
}