Golang Tutorials

Pointer Basics

Let's start with a pointer definition.

In computer science, a pointer is a programming language object that stores the memory address of another value located in computer memory.

Now, Golang is a pass-by-value language. Which means that you are never passing references of objects in the traditional sense... we overcome this limitation by having the ability to pass around the memory location that contains a real value like the number 5, or a string "Cheshire Cat".

This memory address is pointed to by none other than the pointer.

Fresh Main.Go

package main

func main() {
    // blank slate
}

Next we are going to add a variable assignment and fmt.Printf().

package main

import "fmt"

func main() {
	// declare a variable and assign a value to it
	myString := "newstring"
	fmt.Printf("%v\n", myString)
}

This outputs newstring.


Now lets try and update the value with a function.

package main

import "fmt"

func main() {
	// declare a variable and assign a value to it
	myString := "newstring"
	fmt.Printf("%v\n", myString)

	updateValue1(myString)
	fmt.Printf("%v\n", myString)
}

// pass by value
func updateValue1(input string) {
	input = "update"
}

Here we are assigning a value to the string input, but the values do not persist. It assigned the value "update" to the local string input, which was a localized copy of myString.

This is the output

newstring
newstring

This is the by far the most common issue for developers new to, or unfamiliar with, pointers and addresses.

Pass By Reference (still Values)

A lot of high level languages, like C# and Java, handle the majority of pass by value and pass by reference behind close doors. You learn the language rules/paradigms, but by and far... don't ever really have to think about this kind of thing. This is inherently good or bad, just different.

We have to use language operators to achieve similar functionality.

The Asterisk & The Ampersand

// * is used to indicate a pointer reference and derference a pointer
myvar1 := 5
mypointer1 := &myvar1 // int pointer that points to the memory address of myvar1
*mypointer1 = 5 // assigning 5 to memory value of mypointer
mypointer1 = 5 // explosion, since mypointer is of type *int, not an int.

The rules are pretty simple when using pointers. Unless you are using pointers and dereferencing you are only ever making local changes to to local variables.

When needing to pass around the memory addresses you give your variable a & tag prefix.

When you are expecting an address in a function or receiver, you tell the compiler it is a pointer by * tag prefix on the data type.

When you are needing to assign a value into the location a pointer is pointing to, first dereference a variable name by * tag prefix.

When you are needing to assign another memory addresses into a pointer, you don't derference the pointer variable name and assign to the variable address by &variable format.

There are so many rules and words... let's use the * and & to determine what we can do with them.

package main

import (
	"fmt"
)

func main() {
	myvar1 := 1
	myvar2 := 100
	
	fmt.Printf("Value %v || Value %v\n", myvar1, myvar2)
	
	// memory addresses assigned to the pointers of myvar1, myvar2
	mypointer1 := &myvar1
	mypointer2 := &myvar2
	
	// dereference to assign value int into the int memory address
	*mypointer1 = 5 // assign to the value in the memory address that myvar1 represents
	*mypointer2 = 20 // assign to the value in the memory address that myvar2 represents
	
	fmt.Printf("Value %v || Value %v\n", myvar1, myvar2)
	
	// print address of the pointers
	fmt.Printf("Address %v || Address %v\n", &mypointer1, &mypointer2)
	
	// print the addresses the pointers are pointing to
	fmt.Printf("Address %v || Address %v\n", mypointer1, mypointer2)
	
	// print the values the pointers are pointing to by using dereference
	fmt.Printf("Values %v || Values %v\n", *mypointer1, *mypointer2)
	
	// print the addresses of the variables
	fmt.Printf("Address %v || Address %v\n", &myvar1, &myvar2)
	
	// print the values of the variables
	fmt.Printf("Values %v || Values %v\n", myvar1, myvar2)
	
	// assign address 1 into 2
	mypointer2 = mypointer1
	
	// print address of the pointers, these should still be different
	fmt.Printf("Address %v || Address %v\n", &mypointer1, &mypointer2)
	
	// print the addresses the pointers are pointing to, these should be identical now
	fmt.Printf("Address %v || Address %v\n", mypointer1, mypointer2)
	
	// print the values the pointers are pointing to by using dereference, these values should be identical now
	fmt.Printf("Values %v || Values %v\n", *mypointer1, *mypointer2)
}

The thing that makes sense but you often forget, pointers also exist in memory. So you have to use caution when referencing/dereferencing or you could be assigning to a pointer address. This == explosion.

Sample Output

Value 1 || Value 100
Value 5 || Value 20
Address 0x40c130 || Address 0x40c138 // different
Address 0x414020 || Address 0x414024 // different
Values 5 || Values 20                // different
Address 0x414020 || Address 0x414024 // different
Values 5 || Values 20                // different
Address 0x40c130 || Address 0x40c138 // different
Address 0x414020 || Address 0x414020 // identical
Values 5 || Values 5                 // identical

This is one of those things that just get better with practice and experimentation. You get some rules and you try to adhere to them. Let's get back to our example and try an update some memory addresses.

package main

import "fmt"

func main() {
	// declare a variable and assign a value to it
	myString := "newstring"
	fmt.Printf("%v\n", myString)

	updateValue1(myString)
	fmt.Printf("%v\n", myString)

	updateValue2(&myString) // passing address
	fmt.Printf("%v\n", myString)
}

// pass by value
func updateValue1(input string) {
	input = "update"
}

// pass by reference
func updateValue2(input *string) {
	(*input) = "update" // dereferencing address
}

Output

newstring
newstring
update

Now we are cooking. Let's do the same with a pair of integers.

package main

import "fmt"

func main() {
	myFirstNumber := 5
	mySecondNumber := 10
	fmt.Printf("FirstNumber: %v SecondNumber: %v\n", myFirstNumber, mySecondNumber)

	swapValue1(myFirstNumber, mySecondNumber)
	fmt.Printf("FirstNumber: %v SecondNumber: %v\n", myFirstNumber, mySecondNumber)
}

// pass by value
func swapValue1(input1 int, input2 int) {
	tmp := input2
	input2 = input1
	input1 = tmp
}

Output

FirstNumber: 5 SecondNumber: 10
FirstNumber: 5 SecondNumber: 10

Exact same issue again. We are swapping values internally in the method but the real values never get updated. Let's apply the same method of value assignment to memory addresses.

package main

import "fmt"

func main() {
	myFirstNumber := 5
	mySecondNumber := 10
	fmt.Printf("FirstNumber: %v SecondNumber: %v\n", myFirstNumber, mySecondNumber)

	swapValue1(myFirstNumber, mySecondNumber)
	fmt.Printf("FirstNumber: %v SecondNumber: %v\n", myFirstNumber, mySecondNumber)

	swapValue2(&myFirstNumber, &mySecondNumber)
	fmt.Printf("FirstNumber: %v SecondNumber: %v\n", myFirstNumber, mySecondNumber)
}

// pass by value
func swapValue1(input1 int, input2 int) {
	tmp := input2
	input2 = input1
	input1 = tmp
}

// pass by reference, input1 and input2 are both pointers
func swapValue2(input1 *int, input2 *int) {
	tmp := *input2    // create an int and assign input2 value (deref)
	*input2 = *input1 // input1 value (deref) into input2 value (deref)
	*input1 = tmp     // input2 value (from tmp) into input1 (deref)
}

Output

FirstNumber: 5 SecondNumber: 10
FirstNumber: 5 SecondNumber: 10
FirstNumber: 10 SecondNumber: 5
GREAT SUCCESS!

Additional Links

Everything Together

package main

import "fmt"

func main() {
	// declare and assign local value
	myString := "newstring"
	fmt.Printf("%v\n", myString)

	updateValue1(myString)
	fmt.Printf("%v\n", myString)

	updateValue2(&myString)
	fmt.Printf("%v\n", myString)

	myFirstNumber := 5
	mySecondNumber := 10
	fmt.Printf("FirstNumber: %v SecondNumber: %v\n", myFirstNumber, mySecondNumber)

	swapValue1(myFirstNumber, mySecondNumber)
	fmt.Printf("FirstNumber: %v SecondNumber: %v\n", myFirstNumber, mySecondNumber)

	swapValue2(&myFirstNumber, &mySecondNumber)
	fmt.Printf("FirstNumber: %v SecondNumber: %v\n", myFirstNumber, mySecondNumber)
}

// pass by value
func updateValue1(input string) {
	input = "update"
}

// pass by reference
func updateValue2(input *string) {
	(*input) = "update"
}

// pass by value
func swapValue1(input1 int, input2 int) {
	tmp := input2
	input2 = input1
	input1 = tmp
}

// pass by reference, input1 and input2 are both pointers
func swapValue2(input1 *int, input2 *int) {
	tmp := *input2    // create an int of input2 value (deref)
	*input2 = *input1 // input1 value (deref) into input2 value (deref)
	*input1 = tmp     // input2 value (from tmp) into input1 (deref)
}

Play.Golang.Org Sandbox

I have embedded this source location for you to run and see outputs.