-1

When the formal parameter is map, assigning a value directly to a formal parameter cannot change the actual argument, but if you add a new key and value to the formal parameter, the actual argument outside the function can also be seen. Why is that?

I don't understand the output value of the following code, and the formal parameters are different from the actual parameters.

unc main() {
    t := map[int]int{
        1: 1,
    }
    fmt.Println(unsafe.Pointer(&t))
    copysss(t)
    fmt.Println(t)
}
func copysss(m map[int]int) {
    //pointer := unsafe.Pointer(&m)
    //fmt.Println(pointer)
    m = map[int]int{
        1: 2,
    }
}
stdout :0xc000086010

        map[1:1]
func main() {
    t := map[int]int{
        1: 1,
    }
    fmt.Println(unsafe.Pointer(&t))
    copysss(t)
    fmt.Println(t)
}
func copysss(m map[int]int) {
    //pointer := unsafe.Pointer(&m)
    //fmt.Println(pointer)
    m[1] = 2
}
stdout :0xc00007a010

        map[1:2]
func main() {
    t := map[int]int{
        1: 1,
    }
    fmt.Println(unsafe.Pointer(&t))
    copysss(t)
    fmt.Println(t)
}
func copysss(m map[int]int) {
    pointer := unsafe.Pointer(&m)
    fmt.Println(pointer)
    m[1] = 2
}
stdout:0xc00008a008
       0xc00008a018
       map[1:2]

I want to know if the parameter is a value or a pointer.

Wongfy
  • 47
  • 3

2 Answers2

2

The parameter is both a value and a pointer.

Wait.. whut?

Yes, a map (and slices, for that matter) are types, pretty similar to what you would implement. Think of a map like this:

type map struct {
    // meta information on the map
    meta struct{
        keyT   type
        valueT type
        len    int
    }
    value *hashTable // pointer to the underlying data structure
}

So in your first function, where you reassign m, you're passing a copy of the struct above (pass by value), and you're assigning a new map to it, creating a new hashtable pointer in the process. The variable in the function scope is updated, but the one you passed still holds a reference to the original map, and with it, the pointer to the original map is preserved.

In the second snippet, you're accessing the underlying hash table (a copy of the pointer, but the pointer points to the same memory). You're directly manipulating the original map, because you're just changing the contents of the memory.

So TL;DR

A map is a value, containing meta information of what the map looks like, and a pointer to the actual data stored inside. The pointer is passed by value, like anything else (same way pointers are passed by value in C/C++), but of course, dereferencing a pointer means you're changing the values in memory directly.

Careful...

Like I said, slices work pretty much in the same way:

type slice struct {
    meta struct {
        type T
        len, cap int
    }
    value *array // yes, it's a pointer to an underlying array
}

The underlying array is of say, a slice of ints will be [10]int if the cap of the slice is 10, regardless of the length. A slice is managed by the go runtime, so if you exceed the capacity, a new array is allocated (twice the cap of the previous one), the existing data is copied over, and the slice value field is set to point to the new array. That's the reason why append returns the slice that you're appending to, the underlying pointer may have changed etc.. you can find more in-depth information on this.

The thing you have to be careful with is that a function like this:

func update(s []int) {
    for i, v := range s {
       s[i] = v*2
    }
}

will behave much in the same way as the function you have were you're assigning m[1] = 2, but once you start appending, the runtime is free to move the underlying array around, and point to a new memory address. So bottom line: maps and slices have an internal pointer, which can produce side-effects, but you're better off avoiding bugs/ambiguities. Go supports multiple return values, so just return a slice if you set about changing it.

Notes:

In your attempt to figure out what a map is (reference, value, pointer...), I noticed you tried this:

pointer := unsafe.Pointer(&m)
fmt.Println(pointer)

What you're doing there, is actually printing the address of the argument variable, not any address that actually corresponds to the map itself. the argument passed to unsafe.Pointer isn't of the type map[int]int, but rather it's of type *map[int]int.

Personally, I think there's too much confusion around passing by value vs passing by . Go works exactly like C in this regard, just like C, absolutely everything is passed by value. It just so happens that this value can sometimes be a memory address (pointer).


More details (references)

  • Slices: usage & internals
  • Maps Note: there's some confusion caused by this one, as pointers, slices, and maps are referred to as *reference types*, but as explained by others, and elsewhere, this is not to be confused with C++ references
Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149
0

In Go, map is a reference type. This means that the map actually resides in the heap and variable is just a pointer to that.

The map is passed by copy. You can change the local copy in your function, but this will not be reflected in caller's scope.

But, since the map variable is a pointer to the unique map residing in the heap, every change can be seen by any variable that points to the same map.

This article can clarify the concept: https://www.ardanlabs.com/blog/2014/12/using-pointers-in-go.html.

Giulio Micheloni
  • 1,290
  • 11
  • 25
  • You can't be sure that the map resides in heap IIRC. The runtime manages the underlying hashtable pointer, it could be a pointer to stack memory, or heap... you just don't know. Furthermore, you can change what's in the map because of the pointer, but since the underlying pointer is _managed_, it's possible that adding new keys might result in a new hashtable being allocated, and the pointer in the copy getting updated, at which point, the map in the caller scope won't be affected – Elias Van Ootegem May 23 '19 at 11:47