2

I want to find out why

x:= odsMap[segRef]
x.GetValue("@OriginDestinationKey")

works, but this does not:

odsMap[segRef].GetValue("@OriginDestinationKey")

?

The last snippet prints the following errors:

cannot call pointer method on odsMap[segRef]go
cannot take the address of odsMap[segRef]

These errors happen during compilation time (not runtime). So, my main question is why I need an intermediate variable x to access the function?

Regarding the type of the variables odsMap is a map[string] XMLElement and segRef is a string.

Thanks.

icza
  • 389,944
  • 63
  • 907
  • 827
Eloy Fernández Franco
  • 1,350
  • 1
  • 24
  • 47

2 Answers2

13

Map index expressions are not addressable, because the internals of a map may change when a new entry is added to it, so the spec intentionally does not allow taking its address (this gives greater freedom for map implementations).

This means if you store non-pointers in the map, and you want to call a method of a stored value that has a pointer receiver, that would require to take the address of the non-pointer value (to be used as the receiver), but since map index expressions are not addressable, that results in a compile-time error.

A workaround is to store pointer values in the map, so there is no need to take the address of an index expression, because it's already a pointer. An example of this can be seen in this answer: Why should constructor of Go return address? If we have this type:

type My int

func (m *My) Str() string { return strconv.Itoa(int(*m)) }

This gives the compile-time error in question:

m := map[int]My{0: My(12)}
m[0].Str() // Error!

But this works:

m := map[int]*My{}
my := My(12)
m[0] = &my // Store a pointer in the map

m[0].Str() // You can call it, no need to take the address of m[0]
           // as it is already a pointer

Another option is to assign it to a local variable whose address can be taken, and call the pointer method on that. Care must be taken though, as if the method has pointer receiver, it might modify pointed object or its components (e.g. fields of a struct), which would not be reflected in the value stored in the map. If you go down this path, you might have to reassign the value to the key in the map to have the updated value.

All-in-all, if you have a value whose type has methods with pointer receiver, you're better off using it (store, pass) as a pointer and not as a non-pointer value.

See related questions:

Pointer methods on non pointer types

How can I store reference to the result of an operation in Go?

icza
  • 389,944
  • 63
  • 907
  • 827
  • Assigning to the local variable may also require re-assigning the value back in the map in case if the method uses the pointer to update the local value. – bereal Aug 02 '19 at 07:49
  • @bereal Yes, you're right. Assigning it to a local variable creates a copy of the value stored in the map. – icza Aug 02 '19 at 07:53
  • Thanks @icza understood!! :) – Eloy Fernández Franco Aug 02 '19 at 08:37
  • @icza: "internals of a map may change when a new entry is added to it." when an entry is added or deleted. – peterSO Aug 02 '19 at 09:13
  • there might be one _big_ drawback of resorting to using pointer map element - GC pressure - using value types inside a map would free the GC of tracking the individual value element - while using pointer to value elements, if the pointed to value are individually allocated on the heap (without any value pooling), a map with millions of elements would bring GC tracking to its knees - but of course, a system designed to handle millions of elements would probably be better designed, than relying on the primitive support provided by the standard library. – Dejavu Jul 04 '22 at 03:50
1

@icza's answer is the correct one.

Here is an example to illustrate how "value receiver" vs "pointer receiver" interact with "pointer map" vs "values map" :

https://play.golang.org/p/JVp6DirgPkU

package main

import (
    "fmt"
)

// a simple type, with two methods : one with a value receiver, one with a pointer receiver
type Item struct {
    name string
}

func (i Item) GetNameByValue() string {
    return i.name
}

func (i *Item) GetNameByRef() string {
    return i.name
}

func main() {
    {
        // in this map, we store *pointers* to Item values
        mapByRef := make(map[int]*Item)

        mapByRef[0] = &Item{"I am stored as a pointer"}

        // GetNameByRef will work on a *Item : "mapByRef[0]" is already a pointer
        fmt.Println("GetByRef   :", mapByRef[0].GetNameByRef())

        // GetNameByValue will work on a *Item :   go automatically turns this into '(*mapByRef[0]).GetNameByValue()', and this is valid
        fmt.Println("GetByValue :", mapByRef[0].GetNameByValue())
    }

    {
        // in this map, we store Item values (no pointers)
        mapByValue := make(map[int]Item)

        mapByValue[0] = Item{"I am stored as a value"}

        // GetNameByValue will work on a Item :  "mapByValue[0]" has the right type
        fmt.Println("GetByValue :", mapByValue[0].GetNameByValue())

        // GetNameByRef will not work :  go tries to turn this into :  (&mapByValue[0]).GetNameByRef(),
        // and go refuses to let you take the address of a value inside a map

        // fmt.Println("GetByRef   :", mapByValue[0].GetNameByRef())

        // compiler error :
        //   ./prog.go:47:46: cannot call pointer method on mapByValue[0]
        //   ./prog.go:47:46: cannot take the address of mapByValue[0]

        // you will need some way to copy the value before taking its address :
        item := mapByValue[0]
        fmt.Println("item.GetByRef    :", item.GetNameByRef())
        // same as :
        fmt.Println("(&item).GetByRef :", (&item).GetNameByRef())
    }
}

// Output :
//
// GetByRef   : I am stored as a pointer
// GetByValue : I am stored as a pointer
// GetByValue : I am stored as a value
// item.GetByRef    : I am stored as a value
// (&item).GetByRef : I am stored as a value
LeGEC
  • 46,477
  • 5
  • 57
  • 104