Golang: pointers – detailed overview

By | 04/20/2019

What is the pointer?

Shortly, the pointer is a variable which stores an address of another variable, where some data is stored.

A pointer example

Let’s take the simplest example where a pointer is used:

package main

import "fmt"

func main() {

    a := 1
    b := &a

    fmt.Println("A: ", a)
    fmt.Println("B: ", b)
    fmt.Println("B: ", *b)
}

Here:

  1. a variable a is created with the integer type and value 1
  2. a variable b is created with the pointer to the integer type (see below)
  3. and data output:
    1. first just the a value
    2. the value(!) or content of the b variable
    3. finally, we are getting the value of the a, to which the b is pointed to (will look at the * and & operators bit later)

Run the code:

[simterm]

$ go run pointers_example.go 
A:  1
B:  0xc0000140e8
B:  1

[/simterm]

On the second line, we are seeing the memory address, where the b pointer is pointed.

On the third line – we got value from this memory address.

The pointer could be initialized in a more amply way with types specification instead of using := so the code will look like:

...
func main() {

    var a int = 1
    var b *int = &a

    fmt.Println("A: ", a)
    fmt.Println("B: ", b)
    fmt.Println("B: ", *b)

}
...

In the var b *int = &a line we set that the b variable is a pointer to the integer data.

In the same way, a pointer to the string data could be created just with the *string instead of the *int in its data type:

...
    var c *string  
    fmt.Printf("The C var type: %T, default value: %v\n", c, c)
    var d string = "This is a string"
    c = &d
    fmt.Printf("The C var type: %T, default value: %v, string value: %s\n", c, c, *c) 
...

here:

  1. the c variable is created as a pointer to the string data
  2. using Printf()‘s modifiers the c variable’s data type (%T) and its value (%v) are displayed
  3. the d variable is created with the string data type and “This is a string” value
  4. for the c variable the memory address of the d variable is set
  5. using Printf()‘s modifiers the c variable’s data type (%T), its value (%v), and the value from the memory address which is stored in the c

Run:

[simterm]

$ go run pointers_example.go 
The C var type: *string, default value: <nil>
The C var type: *string, default value: 0xc0000101e0, string value: This is a string

[/simterm]

* and & operators

We already used them in examples above but let’s take a closer look.

The * operator is a dereference operator.

The dereference here means that we are getting a value not of a pointer (which stores an address) but from the memory address where this pointer is… Well – pointed to 🙂

Let go back to our previous example:

...
fmt.Printf("The C var type: %T, default value: %v, string value: %s\n", c, c, *c)
...

Here:

  • default value: %v with the с – displays a value which is stored in the  c variable – a memory address, where c is pointed to
  • string value: %s with the – displays a value which is got after calling the memory from the c variable

The & operator returns a variable’s memory address.

For example, let’s add to our previous example one more line and lets display addresses using Printf() with %p modifier:

...
    var c *string  
    fmt.Printf("The C var type: %T, default value: %v\n", c, c)
    var d string = "This is a string"
    c = &d
    fmt.Printf("The C var type: %T, default value: %v, string value: %s\n", c, c, *c)
    fmt.Printf("The D adress: %p\nThe C address: %p\nThe C value: %v\n", &d, &c, c)
...

Check:

[simterm]

$ go run pointers_example.go 
The C var type: *string, default value: <nil>
The C var type: *string, default value: 0xc0000101e0, string value: This is a string

The D adress: 0xc0000101e0
The C address: 0xc00000e028
The C value: 0xc0000101e0

[/simterm]

Here we got 0xc0000101e0 value as the d variable address, 0xc00000e028 as the address of the c variable, but the c itself stores address of the d variable – 0xc0000101e0.

Actually a data initialization in the c pointer variable is done by getting the address of the d variable:

...
c = &d
...

The new() function

To define and initialize a pointer using the var pointername *type notation – you can use the builtin new() Go function which accepts a data type as the first argument and will return a pointer to a memory allocated for a variable’s data:

...
    a := 1
    b := new(int)
    fmt.Printf("A: %d, B: %v, %v\n", a, b, *b)
    b = &a
    fmt.Printf("A: %d, B: %v, %v\n", a, b, *b) 
...

Here:

  1. the a variable is created with value 1
  2. the b variable is created which will hold a pointer to the memory returned by the new() function and which keeps 0 for now, as already allocated memory can’t hold nil
  3. the b‘s value is updated with the a‘s address

Check:

[simterm]

$ go run pointers_example.go 
A: 1, B: 0xc000014100, 0
A: 1, B: 0xc0000140e8, 1

[/simterm]

Changing a pointer’s value

Well, this is not correct to say “changing a pointer’s value” as a pointer variable stores a memory address – not a value itself.

But using a pointer we can change this value in a memory location to which this pointer is pointed to.

For example:

...
    a := 1
    b := &a

    fmt.Println("A: ", a)
    fmt.Println("B: ", *b)

    *b = 2
    fmt.Println("B: ", *b)
...

Here:

  1. the a variable has value 1
  2. the b variable has the a‘s address
  3. the a variable value displayed
  4. the value displayed take from the address where the b is pointed to
  5. we are changing the value in this memory to the 2
  6. and displays the new value

Run the code:

[simterm]

$ go run pointers_example.go 
A:  1
B:  1
B:  2

[/simterm]

Passing a pointer as a function’s argument

You can pass a pointer to a function as its argument.

For example:

package main

import "fmt"

func setVal(b *int) {
    *b = 2
}

func main() {

    a := 1
    b := &a

    fmt.Println("Init values")
    fmt.Println("A: ", a)
    fmt.Println("B: ", *b)

    setVal(b)

    fmt.Println("Changed values")
    fmt.Println("A: ", a)
    fmt.Println("B: ", *b)
}

Here we are creating a setVal() function which accepts an integer pointer as an argument and will change a value in the address passed with this pointer.

Check it:

[simterm]

$ go run pointers_example.go 
Init values
A:  1
B:  1
Changed values
A:  2
B:  2

[/simterm]

After calling the setVal() –  thea and b will display a new value.

Even more – we could pass just an address of the a‘s variable to the setVal():

...
func setVal(b *int) {
    *b = 2
}

func main() {

    a := 1

    fmt.Println("Init values")
    fmt.Println("A: ", a)

    setVal(&a)

    fmt.Println("Changed values")
    fmt.Println("A: ", a)
}

The result is:

[simterm]

$ go run pointers_example.go 
Init values
A:  1
Changed values
A:  2

[/simterm]

Functions: passing arguments by value and by reference

A bit offtopic here but using the example above the difference between passing argument by value and argument by reference also can be displayed.

Let’s update this example:

...
func setVal(b *int, c int) {
    *b = 2
    c = 4
    fmt.Printf("B from setVal(). Poiner to: %p, val: %v\n", b, *b)
    fmt.Printf("C from setVal(). Addr: %p, val: %v\n", &c, c)
}

func main() {

    a := 1
    b := &a
    c := 3

    fmt.Println("Init values")
    fmt.Printf("A from main(). Addr: %p, val: %v\n", &a, a)
    fmt.Printf("B from main(). Poiner to: %p, val: %v\n", b, *b)
    fmt.Printf("C from main(). Addr: %p, val: %v\n", &c, c)

    fmt.Println("Changed values")
    setVal(b, c)
    
    fmt.Printf("A from main(). Addr: %p, val: %v\n", &a, a)
    fmt.Printf("B from main(). Poiner to: %p, val: %v\n", b, *b)
    fmt.Printf("C from main(). Addr: %p, val: %v\n", &c, c)
}

Here:

  1. get the a variable address and its value
  2. get the address which is stored in the b variable and then the data which is stored on this address
  3. get the c variable address and its value
  4. call the setVal()function and pass the a‘s value by the reference in the b and the c – as a value
  5. in the setVal() getting the address which is stored in the b variable and the value which is stored there
  6. in the setVal() getting the c variable address and the value which is stored in this memory area
  7. in the main() getting the a‘s address and the value stored there
  8. in the main() getting the address which is stored in the b variable and value from there
  9. in the main() getting the c variable address and the value which is stored there

Run it:

[simterm]

$ go run pointers_example.go 
Init values
A from main(). Addr: 0xc0000140e8, val: 1
B from main(). Poiner to: 0xc0000140e8, val: 1
C from main(). Addr: 0xc000014100, val: 3
Changed values
B from setVal(). Poiner to: 0xc0000140e8, val: 2
C from setVal(). Addr: 0xc000014130, val: 4
A from main(). Addr: 0xc0000140e8, val: 2
B from main(). Poiner to: 0xc0000140e8, val: 2
C from main(). Addr: 0xc000014100, val: 3

[/simterm]

Here:

  1. the a is stored in the 0xc0000140e8 location and has value 1
  2.  theb is pointed to the same location 0xc0000140e8 and returns the same 1 value
  3. thec is stored in the 0xc000014100 location with the 3 value
  4. the setVal() is called
  5. theb in the setVal() still pointed to the 0xc0000140e8 location with the 2 as its value
  6. the c in the setVal() got its new address 0xc000014130 where the 4 is stored
  7. thea in the main() now keeps the 2 value from the same 0xc0000140e8 location
  8. the b in the main() still the same as it is in the setVal() – points to the same location and returns the same value
  9. in the main() for the c variable nothing was changed as the c in the setVal() has own address 0xc000014130, but the c in the main() uses the 0xc000014100 location

Actually, that’s all need to know to better understand what pointers are and how to use them.