Pointers in Go: From Basics to Advanced
Pointers are a fundamental concept in Go that enable efficient memory management and data sharing. Let's explore them comprehensively.
Table of Contents
Basic Pointer Concepts
A pointer holds the memory address of a value:
-
&operator generates a pointer to its operand -
operator dereferences a pointer (accesses the underlying value)
-
Zero value of a pointer is
nil
var x int = 10
var p *int = &x // p points to x
fmt.Println(*p) // 10 (dereferencing)
Pointer Declaration
Different ways to declare and use pointers:
// Method 1: Full declaration
var ptr1 *int
num := 42
ptr1 = &num
// Method 2: Short declaration
ptr2 := &num
// Method 3: Using new()
ptr3 := new(int) // Allocates memory, returns pointer
*ptr3 = 100
Pointer Operations
Basic Operations
a := 10
b := &a // b points to a
fmt.Println(*b) // 10 (read through pointer)
*b = 20 // write through pointer
fmt.Println(a) // 20 (original value changed)
Nil Pointers
var p *int // nil pointer
if p != nil {
fmt.Println(*p) // Would panic if dereferenced
}
Pointer Comparison
x := 10
p1 := &x
p2 := &x
fmt.Println(p1 == p2) // true (same memory address)
Pointers with Structs
Value vs Pointer Receivers
type Point struct {
X, Y int
}
// Value receiver (works on copy)
func (p Point) MoveByValue(dx, dy int) {
p.X += dx
p.Y += dy
}
// Pointer receiver (modifies original)
func (p *Point) MoveByPointer(dx, dy int) {
p.X += dx
p.Y += dy
}
p := Point{1, 2}
p.MoveByValue(1, 1) // p unchanged
p.MoveByPointer(1, 1) // p modified
Automatic Dereferencing
Go automatically dereferences struct pointers:
p := &Point{1, 2}
fmt.Println(p.X) // Same as (*p).X
Pointers with Functions
Passing Pointers to Functions
func increment(p *int) {
*p++
}
x := 10
increment(&x)
fmt.Println(x) // 11
Returning Pointers
func createPoint(x, y int) *Point {
return &Point{x, y} // Safe to return address of local variable
}
p := createPoint(5, 10)
Pointer Arithmetic (Absent in Go)
Unlike C/C++, Go doesn't support pointer arithmetic:
arr := [3]int{1, 2, 3}
p := &arr[0]
// p++ // Compile error: invalid operation
Advanced Pointer Patterns
Pointer to Pointer
var x int = 10
var p *int = &x
var pp **int = &p
fmt.Println(**pp) // 10
Function Pointers
var op func(int, int) int
op = func(a, b int) int { return a + b }
result := op(3, 4) // 7
Interface Pointers
var w io.Writer = os.Stdout
pw := &w
(*pw).Write([]byte("hello\\n"))
Linked List Example
type Node struct {
Value int
Next *Node
}
list := &Node{1, &Node{2, &Node{3, nil}}}
Performance Considerations
-
Passing pointers to large structs is more efficient than passing values
-
Pointer indirection has a small performance cost
-
Escape analysis determines if variables are allocated on heap or stack
-
Cache locality can be better with values than pointers
-
Garbage collection overhead increases with many heap allocations
Best Practices
-
Use pointers to modify function arguments
-
Avoid unnecessary pointers - they add complexity
-
Document pointer ownership - who is responsible for the data
-
Check for nil before dereferencing
-
Prefer value receivers for small structs
-
Use pointer receivers when methods need to modify the struct
-
Be consistent with receiver types across all methods of a type
-
Avoid returning pointers to local variables (except when it's safe)
-
Consider sync.Pool for frequently allocated pointer types
-
Profile before optimizing pointer usage
Complete Example
package main
import (
"fmt"
"unsafe"
)
type Person struct {
Name string
Age int
}
func updateName(p *Person, name string) {
p.Name = name
}
func (p *Person) birthday() {
p.Age++
fmt.Printf("%s is now %d years old\\n", p.Name, p.Age)
}
func main() {
// Basic pointer usage
x := 10
ptr := &x
fmt.Println("Value of x through pointer:", *ptr)
// Pointer to struct
person := Person{"Alice", 30}
updateName(&person, "Alicia")
fmt.Println("Updated person:", person)
// Method with pointer receiver
person.birthday()
// Pointer to pointer
pp := &ptr
fmt.Println("Pointer to pointer value:", **pp)
// Unsafe pointer (advanced usage)
bytes := []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f}
str := *(*string)(unsafe.Pointer(&bytes))
fmt.Println("String from bytes (unsafe):", str)
// Function pointer
var op func(int, int) int = func(a, b int) int { return a * b }
fmt.Println("Function pointer result:", op(5, 6))
// Linked list with pointers
type Node struct {
Value int
Next *Node
}
list := &Node{1, &Node{2, &Node{3, nil}}}
for n := list; n != nil; n = n.Next {
fmt.Println("Node value:", n.Value)
}
}