The pointers in the Go language are like in the C language. A pointer holds a memory address for a value. *
operator is used to denote a variable as a pointer of the type, also used in pointer dereferencing, and &
operator is used to getting the underlying value.
A memory address is stored as an unsigned native word. The size of a native word is 4 bytes in 32-bit architectures and 8 bits in 64-bit architectures and indicated by hexadecimal values like 0x1234ABCD
.
A basic example is:
package main
import "fmt"
func main() {
str := "Hello Cosmos"
// Pointer to string
var pStr *string
// pStr points to addr of message
pStr = &str
fmt.Println("Message = ", *pStr)
fmt.Println("Message Address = ", pStr)
// Change message using pointer de-referencing
*pStr = "Hello Universe"
fmt.Println("Message = ", *pStr)
fmt.Println("Message Address = ", pStr)
}
We can pass by reference in Go:
package main
import "fmt"
// pass by reference
func zero(x int) {
x = 0
}
func main() {
x := 5
zero(x)
fmt.Println(x)
}
As well as we can pass by value with pointers:
package main
import "fmt"
// pass by value
func zero(xPtr *int) {
fmt.Println(xPtr) // memory address of x
fmt.Println(*xPtr) // value of x
*xPtr = 0
}
func main() {
x := 5
zero(&x) // pass the memory address of x
fmt.Println(&x) // memory address of x stays the same
fmt.Println(x) // x is 0
}
Output:
$ go run pointers.go
0xc0000aa058
5
0xc0000aa058
0
Pointers in slicing
It would be good if I were writing slices in another post but whenever you see a slice you see pointers in Go. So I think it’s a good place to mention it.
A string is represented by a 2-word structure in the memory. This structure contains a pointer to the string data and its length. The string data is an array of bytes, as in C.
A slice is a reference to a section of an array. So it does not create a copy of the sliced part, it only holds a pointer. A slice is represented by a 3-word structure. This structure contains a pointer to the first element of the sliced part, the slice length, and the capacity. As you know, length is the upper bound for index operations like x[i]
, and capacity is the upper bound for slice operations like x[i:j]
.
Also I saw another thing in a Medium post here:
package main import "fmt" type Dog struct { name string } func buggyLoop() { dogs := []Dog{Dog{"Ghost"}, Dog{"Bruno"}, Dog{"Lucky"}} dogPtrs := []*Dog{} for _, dog := range dogs { fmt.Printf("Dog with name <%s> and pointer: <%p>\n", dog, &dog) dogPtrs = append(dogPtrs, &dog) } for _, dogPtr := range dogPtrs { fmt.Printf("dogPtr with name <%s> and pointer: <%p>\n", dogPtr.name, dogPtr) } } func main() { buggyLoop() }
Output:
Dog with name <{Ghost}> and pointer: <0xc00004c250>
Dog with name <{Bruno}> and pointer: <0xc00004c250>
Dog with name <{Lucky}> and pointer: <0xc00004c250>
dogPtr with name <Lucky> and pointer: <0xc00004c250>
dogPtr with name <Lucky> and pointer: <0xc00004c250>
dogPtr with name <Lucky> and pointer: <0xc00004c250>
This code seems to work when appending to the variable itself, but it seems failed to append to a pointer to the pointer list. It’s because of the append
method’s structure. When we look at the append
‘s source code we can see that the method actually takes slices.
func append(slice []Type, elems ...Type) []Type
And we learned that slice holds only a pointer to values. So here:
for _, dog := range dogs {
dogPtrs = append(dogPtrs, &dog)
}
The for loop copies the value of the next value of dogs
and assigns it to the loop variable dog
. So dog
variable only holds a copy of the value, at the same address every iteration. And append
method appends the address value of the dog
to dogPtrs
array of pointers. At the end of the iteration, the final value of the dog
will be Luck, and dogPtrs
will be holding the address value of the dog
3 times. (Btw, after the loop method, the garbage collector won’t delete the dog
from the memory because dogPtrs
still holds its memory address.)
new & make
There are two different ways to create a data structure in Go, they’re new
and make
. The new returns a pointer to the created structure, while make returns the structure itself, not a pointer to it.
new(T) returns a pointer to a new T
make(T) returns a new T
You cannot create a pointer to T
with make
. This code will not compile:
package main
func main() {
var1 := new(int)
*var1 = 10
var2 := make(*int)
*var2 = 20
}
The compiler will give an error for line var2 := make(*int)
:
.\main.go:7:12: invalid argument: cannot make *int; type must be slice, map, or channel
Resources:
Go Pointers
Go Data Structures
Pointers in Go
Go Web Programming Bootcamp