Other examples:

Pointer

a gentle introduction to Pointers

When assigning a value to a variable in Go, the value is stored at a particular address in the computer’s memory.

Use the reference operator & to get this address, like so:

package main
import "fmt"
func main() {
  var answer int = 42
  fmt.Println(&answer) // prints something like 0x14000122008
}

When using the & operator on a variable it actually returns a pointer.

In simple terms, a pointer is a variable that hols the memory address of another variable. Think of them as “pointing to” a specific spot in memory.

Like the variables that they point to, pointers in Go also have types. A pointer with the type *int can only hold the memory address of an int variable. And a pointer with the type *string can only hold the memory address of a string variable. And so on.

package main
import "fmt"
func main() {
  var answer int = 42
  var answerPtr *int = &answer
  fmt.Println(answerPtr) // prints something like 0x1400012c008
}

Use the deference operator * to read or set the underlying value that a pointer points to. This is often known as indirection.

package main
import "fmt"
func main() {
  answer := 42
  answerPtr := &answer
  fmt.Println(answerPtr)  // prints something like 0x14000122008
  fmt.Println(*answerPtr) // prints 42
  *answerPtr = 99         // use the dereference operator to assign a new value
  fmt.Println(answer)     // prints 99
}

a one-pager to understanding pointers in Go

package main

import "fmt"

func main() {
  // a pointer is a variable that holds the memory address of another variable
  var p1 *int
  x := 42
  y := 40
  // to create a pointer to a variable, use the & operator followed by the variable's name
  p1 = &x
  p2 := &y
  // to access the value of the variable pointed to by a pointer, you use the * operator followed by the pointer variable name
  *p1 = 100

  increment(&y)
  increment(p2)

  fmt.Println(x)
  fmt.Println(p2)
  fmt.Println(&p2)
  fmt.Println(*p2)
}

// passing pointers to functions
// one common use of pointers is to pass a variable to a function by reference, allowing the function to modify the variable directly.
func increment(p *int) {
  *p++
}
package main

import "fmt"

func main() {
  var creature string = "shark"
  var pointer *string = &creature

  fmt.Println("creature = ", creature)
  fmt.Println("pointer = ", pointer)

  fmt.Println("*pointer = ", *pointer)
  fmt.Println("&creature = ", &creature)

  // output
  // creature = shark
  // pointer = 0xc000014260
  // *pointer = shark
  // &creature = 0xc000014260

  var creatureB Creature = Creature{Species: "shark"}
  fmt.Printf("1) %+v\n", creatureB)
  changeCreature(&creatureB)
  fmt.Printf("3) %+v\n", creatureB)
  // output
  // 1) {Species:shark}
  // 2) &{Species:jellyfish}
  // 3) {Species:jellyfish}

  var creatureC *Creature = &Creature{Species: "shark"}
  fmt.Printf("1) %+v\n", creatureC)
  changeCreature(creatureC)
  fmt.Printf("3) %+v\n", creatureC)
  // output
  // 1) {Species:shark}
  // 2) &{Species:jellyfish}
  // 3) &{Species:jellyfish}

  creatureD := &Creature{Species: "shark"}
  fmt.Printf("1) %+v\n", creatureD)
  changeCreature(creatureD)
  fmt.Printf("3) %+v\n", creatureD)
  // output
  // 1) {Species:shark}
  // 2) &{Species:jellyfish}
  // 3) &{Species:jellyfish}
}

type Creature struct {
  Species string
}

func changeCreature(creature *Creature) {
  creature.Species = "jellyfish"
  fmt.Printf("2) %+v\n", creature)
}

Pointer in function

Function argument passing is value copying, which also applies to pointer arguments. When doubleInt(p) is called, a copy of pointer p is created first and then passed to the function doubleInt. The modification on the copy of the pointer p in doubleInt will not reflect on the original pointer p in the main function.

pointer in function sample
package main

import "fmt"

func doubleInt(x int) {
  x = x * 2
}
func main() {
  a := 1
  doubleInt(a)
  fmt.Println(a) // 1
}
package main

import "fmt"

func doubleInt(x *int) {
  *x = *x * 2
  x = nil // update the pointer x to nil
}

func main() {
  a := 1
  p := &a
  doubleInt(p)
  fmt.Println(a) //2 fmt.Println(p == nil) //false
}

NIL pointer

The zero value of a pointer type is nil. if you try to dereference a pointer which points to nothing (nil), it will cause a runtime panic. With that being said, we should always check if the pointer is nil before trying to dereference it.

a nill pointer sample
package main

import "fmt"

func main() {
  var ptr *int
  fmt.Println(ptr) // <nil>
  ptrValue := *ptr // panic: runtime error: invalid memory address or nil
  fmt.Println(ptrValue)
}

What really is unsafe.Pointer?

To get a start, let’s examine its definition in the code:

type Pointer *ArbitraryType

Unlike a *int, which can only point to an int, or a *bool, which is restricted to bool values, unsafe.Pointer has the liberty to point to any arbitrary type, sweet flexibility right?

unsafe.Pointer is a special kind of pointer that turns off the usual safety rules. When you use it, you’re telling the Go compiler: “I know what I’m doing, so trust me”. Be careful, because you’re going around the normal safety checks.

var a int64 = 10
aPtr := unsafe.Pointer(&a)
b := (*float64)(aPtr)