abcalphabet
abcalphabet

Reputation: 1278

Updating struct via pointer of pointer not working

Ok, for some simplified setup, in this example we have three structs comp, agg and cache that look somewhat like this:

type comp struct {
    id  uint64
    val float64
}

type agg struct {
    id   uint64
    vals []*comp
}

type cache struct {
    compMtx sync.RWMutex
    comps map[uint64]*comp
    aggMtx sync.RWMutex
    aggs  map[uint64]*agg
}

Where the cache has the following function to add new comp values, which in the case of the update does not seem to work:

func (c *cache) NewComp(cpNew *comp) {
    compMtx.Lock()
    defer compMtx.Unlock()
    cpOld, ok := c.comps[cpNew.id]
    if ok { // update
        addr := &cpOld // this is of type **comp
        *addr = cpNew
    } else { // new value
        c.comps[cpNew.id] = cpNew
    }
}

The thought behind this method is that by changing where the pointer of the pointer points to, we can ensure that the comp pointers in agg.vals always point to the newest iteration of a given comp object.

As for the reason behind this approach, iterating over the entirety of an agg.vals array to find the index of the given comp object would a) be computationally expensive due to the (rather large) size of the array and would b) require agg to be locked via an internal sync.Mutex during the search to block different threads from accessing the object, both of which are undesirable. Furthermore assume that making agg.value into a map so as to facilitate easy updates is not a possibility.

Since NewComp as implemented above does however not work, my question would be whether there are any obvious mistakes in the function above, or if I made some fundamental error in my thinking here?


As this might be helpful, here also an example in which the update via pointers of pointers works as expected:

type wrks struct {
    id   uint64
    val1 *comp
    val2 *comp
}

func compute() *comp {...}

func updateComp(c **comp) {
    *c = compute()
}

func processObj(o *obj) {
    updateComp(&o.val1)
    updateComp(&o.val2)
}

I fail to see the fundamental difference between the two, but I may have been staring at this for too long at this point.

Upvotes: 2

Views: 221

Answers (1)

icza
icza

Reputation: 417472

You store pointers in the map, so when you get a pointer from it, just modify the pointed value, assign to the pointed value:

cpOld, ok := c.comps[cpNew.id]
if ok { // update
    *cpOld = *cpNew
} else { // new value
    c.comps[cpNew.id] = cpNew
}

See a simplified example on the Go Playground that shows this works.

Your original code doesn't work because cpOld is a pointer, but it's a copy of the pointer stored in the map. If you modify the value of this cpOld variable, that has no effect on the value stored in the map. You can't change values in a map, you can only reassign / store new values in them.

One thing you should keep in mind: the cpNew pointer passed to NewComp() will not be "useful" after the update, because we didn't use the pointer, we just used the pointed value to update the value that was already pointed by the map (a value stored in the map). If you want the pointer to keep pointing to the same value, you have no other choice but to store that pointer in the map, like if it was new:

cpOld := c.comps[cpNew.id] = cpNew

See related question: How to update map values in Go

Upvotes: 1

Related Questions