A pointer holds the memory address of a value, not the value itself. Passing a pointer shares and modifies the original data without copying it.
The *T syntax denotes a pointer to a value of type T. Here, p is a pointer to an integer. Its default zero-value is nil.
var p *intThe & operator generates a pointer to its operand. It takes the memory address of the variable i and assigns it to p.
i := 42
p = &iTo read the underlying value that the pointer is looking at, use the * operator. This is called "dereferencing" the pointer.
fmt.Println(*p)You can also use the * operator to change the underlying value. Modifying *p directly alters the original variable i.
*p = 2121 confirms *p = 21 changed i itself. p and i share the same memory address throughout.
$ go run pointers.go
42
21A pointer is a variable holding an address. p does not contain 7; it contains the place 7 lives.
Point it somewhere else and the arrow moves. Write through it and you change the value at the other end, not a copy.
A pointer holding no address is nil, and reading through it panics.
What pointers are actually for
A function gets copies of its arguments, so it cannot change what the caller passed. Hand it the address instead and it can reach back through the pointer to the original.
This is the whole reason pointers exist in most Go code. Everything else is a variation on it.
func double(n *int) {
*n *= 2
}
x := 10
double(&x)
fmt.Println(x)The same rule decides whether a method can change its receiver. Inc takes *Counter and mutates the real one; IncCopy takes Counter, increments a copy, and drops it on return.
Go calls c.Inc() on an addressable value by taking its address for you, so the call site looks identical either way. Nothing warns you that one of them did nothing.
type Counter struct{ n int }
func (c *Counter) Inc() { c.n++ } // changes the original
func (c Counter) IncCopy() { c.n++ } // changes a copy, then throws it away
c := Counter{}
c.Inc()
c.Inc()
c.IncCopy()
fmt.Println(c.n)Things Go does differently
Returning a pointer to a local variable is safe here, which surprises anyone arriving from C. The compiler notices the value outlives the function and puts it on the heap instead of the stack.
There is no dangling pointer to create, so a constructor handing back a pointer to a value it just made is ordinary Go rather than a bug waiting to happen.
func newCounter(start int) *Counter {
c := Counter{n: start}
return &c
}
p := newCounter(5)
p.Inc()
fmt.Println(p.n)A method with a pointer receiver can be called on a nil pointer. The receiver is simply nil inside, and a method that checks for it works.
That is what lets the recursive Sum stop: the last node's Next is nil, and calling Sum on it returns 0 rather than panicking. Reading a field through a nil pointer still panics; only the method call is safe.
type Node struct {
Val int
Next *Node
}
func (n *Node) Sum() int {
if n == nil {
return 0
}
return n.Val + n.Next.Sum()
}
list := &Node{1, &Node{2, &Node{3, nil}}}
fmt.Println(list.Sum())
var empty *Node
fmt.Println(empty.Sum())Go gives you pointers for reaching a caller's value and for saying "nothing here", and takes away the parts that make them dangerous: no arithmetic on them, and no way to end up pointing at memory that has gone.
$ go run pointers.go
20
2
6
6
0