Skip to main content
โšก Calmops

Go Pointers: Memory Management and Dereferencing

Go Pointers: Memory Management and Dereferencing

Pointers are fundamental to Go programming. Understanding pointers is essential for writing efficient and correct Go code.

Pointer Basics

Creating Pointers

package main

import "fmt"

func main() {
    x := 42
    p := &x  // Address-of operator

    fmt.Println(x)   // 42
    fmt.Println(p)   // 0xc0000b2008 (memory address)
    fmt.Println(*p)  // 42 (dereference)
}

Pointer Types

package main

import "fmt"

func main() {
    var i int = 42
    var p *int = &i  // Pointer to int

    var s string = "hello"
    var ps *string = &s  // Pointer to string

    var arr [3]int = [3]int{1, 2, 3}
    var pa *[3]int = &arr  // Pointer to array

    fmt.Println(p, ps, pa)
}

Dereferencing

Reading Values

package main

import "fmt"

func main() {
    x := 42
    p := &x

    fmt.Println(*p)  // 42
    fmt.Println(*p + 10)  // 52
}

Modifying Values

package main

import "fmt"

func main() {
    x := 42
    p := &x

    *p = 100
    fmt.Println(x)  // 100
    fmt.Println(*p)  // 100
}

Nil Pointers

Checking for Nil

package main

import "fmt"

func main() {
    var p *int  // nil pointer

    if p == nil {
        fmt.Println("Pointer is nil")
    }

    x := 42
    p = &x

    if p != nil {
        fmt.Println("Pointer is not nil:", *p)
    }
}

Avoiding Nil Dereference

package main

import "fmt"

func getValue(p *int) int {
    if p == nil {
        return 0
    }
    return *p
}

func main() {
    fmt.Println(getValue(nil))  // 0

    x := 42
    fmt.Println(getValue(&x))  // 42
}

Pointers to Structs

Accessing Struct Fields

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    p := Person{Name: "Alice", Age: 30}
    ptr := &p

    // Dereference and access field
    fmt.Println((*ptr).Name)

    // Shorthand (Go allows this)
    fmt.Println(ptr.Name)

    // Modify through pointer
    ptr.Age = 31
    fmt.Println(p.Age)  // 31
}

Pointers as Function Parameters

Pass by Reference

package main

import "fmt"

func increment(p *int) {
    *p++
}

func main() {
    x := 42
    increment(&x)
    fmt.Println(x)  // 43
}

Modifying Structs

package main

import "fmt"

type Counter struct {
    count int
}

func (c *Counter) Increment() {
    c.count++
}

func (c *Counter) Get() int {
    return c.count
}

func main() {
    counter := Counter{count: 0}
    counter.Increment()
    counter.Increment()
    fmt.Println(counter.Get())  // 2
}

Pointers to Pointers

Double Pointers

package main

import "fmt"

func main() {
    x := 42
    p := &x      // Pointer to int
    pp := &p     // Pointer to pointer to int

    fmt.Println(x)    // 42
    fmt.Println(*p)   // 42
    fmt.Println(**pp) // 42

    **pp = 100
    fmt.Println(x)    // 100
}

Memory Allocation

Using new()

package main

import "fmt"

func main() {
    // new() allocates memory and returns pointer
    p := new(int)
    fmt.Println(*p)  // 0 (zero value)

    *p = 42
    fmt.Println(*p)  // 42

    // For structs
    type Person struct {
        Name string
    }

    person := new(Person)
    person.Name = "Alice"
    fmt.Println(person.Name)
}

Using make()

package main

import "fmt"

func main() {
    // make() for slices, maps, channels
    slice := make([]int, 5)
    fmt.Println(slice)  // [0 0 0 0 0]

    m := make(map[string]int)
    m["age"] = 30
    fmt.Println(m)

    ch := make(chan int)
    fmt.Println(ch)
}

Escape Analysis

Stack vs Heap

package main

import "fmt"

func stackAllocation() *int {
    x := 42
    return &x  // x escapes to heap
}

func heapAllocation() *int {
    return new(int)  // Allocated on heap
}

func main() {
    p1 := stackAllocation()
    p2 := heapAllocation()

    fmt.Println(*p1, *p2)
}

Best Practices

โœ… Good: Check for Nil

// DO: Always check for nil pointers
func process(p *Data) error {
    if p == nil {
        return fmt.Errorf("data is nil")
    }
    // Process p
    return nil
}

โŒ Bad: Assume Non-Nil

// DON'T: Assume pointer is not nil
func process(p *Data) {
    p.Field = "value"  // Panic if p is nil
}

โœ… Good: Use Pointer Receivers for Mutations

// DO: Use pointer receivers when modifying
func (p *Person) SetAge(age int) {
    p.Age = age
}

โœ… Good: Avoid Unnecessary Pointers

// DO: Use values when not modifying
func (p Person) GetName() string {
    return p.Name
}

โœ… Good: Document Pointer Semantics

// DO: Be clear about pointer semantics
// NewUser creates a new User and returns a pointer
func NewUser(name string) *User {
    return &User{Name: name}
}

Summary

Go pointers provide:

  1. Reference semantics for efficient data passing
  2. Mutation through pointer receivers
  3. Memory efficiency by avoiding copies
  4. Flexibility in function design
  5. Control over memory allocation

Understanding pointers is crucial for writing idiomatic Go code.

Comments