In Go there are no reference types like you have them in C++. In Go everything is passed by value. When the term "reference type" is used in Go, it means a type that references to the data they ought to represent (via pointers).
Slices are small, struct-like data structures represented by the type reflect.SliceHeader
:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
It contains a pointer to the first element of the slice in an underlying array (SliceHeader.Data
field). This struct is small and is efficient to pass as a value, no need to pass its address (and dereference it to access indirectly any of its fields). The elements of a slice are not stored in the slice header, but in an array outside of the header's memory area. This means modifying a "pointed" element will modify the element of the original slice.
When you append (more than 0) elements to a slice, the Len
field in the header must change, so the new slice that describes the slice with the additional elements must be a different than the one before the append, that's why you need to assign the return value of the builtin append()
function. (Other values may also change, but Len
sure must change.)
Maps are implemented as pointers to the runtime.hmap
structure:
type hmap struct {
// Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
// Make sure this stays in sync with the compiler's definition.
count int // # live cells == size of map. Must be first (used by len() builtin)
flags uint8
B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
hash0 uint32 // hash seed
buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)
extra *mapextra // optional fields
}
As you can see, this is a lot more complex data structure that a slice header, and is a lot bigger, passing this as value would not be efficient.
Adding / removing elements (key-value pairs) from a map is stored in buckets that are referenced by the fields of this struct, but since maps are handled as pointers under the hood, you do not need to assign the result of such operations.
To be complete, channels are also implemented as pointers, pointing to the runtime
package's hchan
type:
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}
This is again a "fat" struct and is handled like map values.
See related questions:
slice vs map to be used in parameter
Appending to a slice with enough capacity using value receiver
Are golang slices pass by value?
What do "value semantics’" and "pointer semantics" mean in Go?