notes blog about

Go has pointers 👉. A pointer is a value that points to the memory address of a variable.

func main() {
	a := 200
	b := &a
	fmt.Printf("%v\n", a) // 200
	fmt.Printf("%v\n", b) // 0xc000018030
}

image

How do they work

You can think of computer memory (RAM) as a sequence of boxes. Each box is labeled with a number. These numbers increment sequentially (1, 2, 3 …). These numbers are called memory addresses. Memory address denotes a piece of storage that can contain a value. A variable is a convenient, alphanumeric pseudonym for a memory address.

var x int32 = 10  // 4 bytes at address 1, holds value 10
var y bool = true // 1 byte at address 5, holds value 1
px := &x          // 4 bytes at address 6, holds value 1 (memory address of x)
py := &y          // 4 bytes at address 10, holds value 5 (memory address of y)
var pz *string    // 4 bytes at address 14, holds no value (nil)

Value    |  0 |  0 |  0 | 10 |  1 |  0 |  0 |  0 |  1 |  0 |  0 |  0 |  5 |  0 |  0 |  0 |  0 |
---------|----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
Address  |  1 |  2 |  3 |  4 |  5 |  6 |  7 |  8 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
---------|----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
Variable | x                 | y  | px                | py                | pz                |

Pointers are always only a machine word in size wide (usually 32 or 64 bits) no matter what type they point to.

How to work with them

The type *T is a pointer to a T value. Its zero value is nil. nil is an untyped identifier (in the universe block) that represents a lack of value for pointer types.

var p *int  // The * here means that p holds a pointer to int.
i := 42
p = &i      // The & operator generates a pointer to its operand.
*p = 21     // The * operator denotes the pointer's underlying value.

The operation on the last line is known as “dereferencing” or “indirecting”. Before dereferencing a pointer you must make sure it’s not nil.

var p *int
fmt.Println(p == nil) // true
fmt.Println(*p)       // panics

new and &

The built-in new function creates a pointer to zero value of the given type.

var x = new(int)
fmt.Println(x == nil) // false
fmt.Println(*x)       // 0

new is rarely used though because you can take address of a struct literal.

x := &Foo{}

You can’t use & before primitive literals (numbers, booleans and strings) or a constant.

type person struct {
    FirstName  string
    LastName   *string
}

p := person{
    FirstName:  "Pat",
    LastName:   &"Peterson",  // This line won't compile
}

Use a helper function to turn a constant value into a pointer.

func stringp(s string) *string {
    return &s
}

p := person{
    FirstName: "Pat",
    LastName: stringp("Peterson"), // This works
}

Pointer and non-pointer types

Types implemented with pointers: slices, maps, functions, channels, interfaces

Non-pointer types: primitives (numbers, booleans and strings), structs, arrays

Method receivers

There are two reasons to use a pointer receiver:

  1. so that the method can modify the value the receiver points to
  2. avoid copying the value on each method call (this can be more efficient if the receiver is a large struct, for example)

In general, all methods on a given type should have either value or pointer receivers, but not a mixture of both.

Mutability

Sources and more