Loading...
Loading...

Go Pointers Tutorial

Pointers in Go provide direct memory access and enable efficient data manipulation. They hold memory addresses of values rather than the values themselves, allowing functions to modify their arguments and manage data structures efficiently.

1. Pointer Basics

Pointers are declared with * and the address-of operator is &.

// Basic pointer usage
var x int = 10
var p *int = &x  // p holds address of x

fmt.Println(x)   // 10 (value)
fmt.Println(p)   // 0x1400012c008 (address)
fmt.Println(*p)  // 10 (dereferenced value)

// Modifying through pointer
*p = 20
fmt.Println(x)   // 20 (changed through pointer)

// Zero value of pointer is nil
var nilPtr *string
fmt.Println(nilPtr) // <nil>

Key Characteristics:

  • *T is a pointer to a value of type T
  • & operator generates a pointer to its operand
  • * operator dereferences a pointer to access the underlying value
  • Zero value is nil (no memory allocated)

Pointer Basics Quiz

What happens when you dereference a nil pointer?

  • Returns the zero value of the type
  • Causes a runtime panic
  • Creates a new zero value automatically

2. Pointers and Functions

Pointers enable functions to modify their arguments (pass-by-reference).

// Value receiver (copy)
func doubleVal(n int) {
    n *= 2
}

// Pointer receiver (original)
func doublePtr(n *int) {
    *n *= 2
}

// Usage
value := 5
doubleVal(value)
fmt.Println(value) // 5 (unchanged)

doublePtr(&value)
fmt.Println(value) // 10 (changed)

// Returning pointers
func createInt(x int) *int {
    return &x  // Go allocates memory appropriately
}

ptr := createInt(42)
fmt.Println(*ptr) // 42

Function Patterns:

  • Use pointers to modify caller's variables
  • Return pointers for large structs to avoid copying
  • Go's escape analysis handles local variable lifetimes
  • Pointer arguments make intent to modify clear

Functions Quiz

When should you pass a pointer to a function?

  • Always, for performance
  • When the function needs to modify the argument
  • Only with primitive types

3. Pointers and Structs

Pointers are commonly used with structs for efficient manipulation.

type Person struct {
    Name string
    Age  int
}

// Value receiver (works on copy)
func (p Person) BirthdayVal() {
    p.Age++
}

// Pointer receiver (works on original)
func (p *Person) BirthdayPtr() {
    p.Age++
}

// Usage
alice := Person{"Alice", 30}
alice.BirthdayVal()
fmt.Println(alice.Age) // 30 (unchanged)

alice.BirthdayPtr()
fmt.Println(alice.Age) // 31 (changed)

// Common constructor pattern
func NewPerson(name string, age int) *Person {
    return &Person{name, age}
}

Struct Guidelines:

  • Pointer receivers are conventional for struct methods
  • Constructor functions often return pointers
  • Go automatically dereferences struct pointers (p.Name vs (*p).Name)
  • Use value receivers for immutable operations

Structs Quiz

Why are pointer receivers common for struct methods?

  • Because Go doesn't support value receivers
  • To allow methods to modify the struct
  • They're required for interface satisfaction

4. Pointer Safety

Go restricts pointer arithmetic for memory safety while providing useful features.

// Go has no pointer arithmetic (unlike C)
x := 10
p := &x
// p++  // Compile error: cannot do pointer arithmetic

// Safe pointer usage
func safeDereference(p *int) int {
    if p != nil {
        return *p
    }
    return 0
}

// Working with arrays
arr := [3]int{1, 2, 3}
arrPtr := &arr
fmt.Println((*arrPtr)[0]) // 1
fmt.Println(arrPtr[1])    // 2 (sugar for array pointers)

// unsafe.Pointer (advanced usage)
var y float64 = 3.14
pY := unsafe.Pointer(&y)
i := *(*int64)(pY)
fmt.Printf("%#x\n", i) // IEEE 754 representation

Safety Features:

  • No pointer arithmetic (prevents buffer overflows)
  • Garbage collection manages memory automatically
  • Nil checks prevent null pointer dereferences
  • unsafe package available for special cases

Safety Quiz

Why doesn't Go allow pointer arithmetic?

  • Because it's too slow
  • For memory safety and security
  • Pointers are only for references

5. Pointers and Slices

Slices contain pointer references to underlying arrays.

// Slice header contains a pointer
s := []int{1, 2, 3}
fmt.Printf("%p\n", &s[0]) // Address of first element

// Passing slices to functions
func modifySlice(s []int) {
    s[0] = 100  // Modifies original (slice contains pointer)
    s = append(s, 4) // May or may not affect caller
}

// Pointer to slice (rarely needed)
func growSlice(s *[]int) {
    *s = append(*s, 5)
}

// Usage
data := []int{1, 2, 3}
modifySlice(data)
fmt.Println(data) // [100 2 3]

growSlice(&data)
fmt.Println(data) // [100 2 3 5]

Slice Behavior:

  • Slices are reference types (contain array pointers)
  • Modifying elements affects the original
  • Appending may create new arrays (capacity changes)
  • Pointer-to-slice needed only when header modification must propagate

Slices Quiz

When would you pass a pointer to a slice?

  • Always, for performance
  • When the function needs to modify the slice header
  • Only with large slices

6. Performance Optimization

Pointers can optimize memory usage but require careful consideration.

// Large struct passing
type BigStruct struct { /* many fields */ }

func processBig(b *BigStruct) { /* ... */ }

// Pointer vs value benchmarks
func BenchmarkValue(b *testing.B) {
    s := BigStruct{}
    for i := 0; i < b.N; i++ {
        processValue(s)
    }
}

func BenchmarkPointer(b *testing.B) {
    s := &BigStruct{}
    for i := 0; i < b.N; i++ {
        processPointer(s)
    }
}

// Reducing allocations
var globalCache = make(map[string]*BigStruct)

func getCached(id string) *BigStruct {
    if p, exists := globalCache[id]; exists {
        return p
    }
    // Load and cache
    p := loadBigStruct(id)
    globalCache[id] = p
    return p
}

Optimization Tips:

  • Use pointers for large structs to avoid copying
  • Measure before optimizing - pointers aren't always faster
  • Cache pointers to reuse allocated objects
  • Consider pointer-heavy data structures (trees, graphs)
  • Be mindful of cache locality with pointer chasing

Performance Quiz

When should you avoid using pointers?

  • With all primitive types
  • When working with small, frequently accessed data
  • In all performance-critical code

7. Common Patterns

Pointers enable several idiomatic Go patterns.

// Linked list
type Node struct {
    Value int
    Next  *Node
}

// Builder pattern
type QueryBuilder struct {
    table  string
    fields []string
}

func (qb *QueryBuilder) Select(fields ...string) *QueryBuilder {
    qb.fields = fields
    return qb
}

func (qb *QueryBuilder) From(table string) *QueryBuilder {
    qb.table = table
    return qb
}

// Optional parameters via pointers
type Config struct {
    Timeout *time.Duration
    Retries *int
}

func NewServer(cfg Config) {
    timeout := 30 * time.Second
    if cfg.Timeout != nil {
        timeout = *cfg.Timeout
    }
    // ...
}

// Object pools
var nodePool = sync.Pool{
    New: func() interface{} { return new(Node) },
}

func getNode() *Node {
    return nodePool.Get().(*Node)
}

Practical Applications:

  • Linked data structures (trees, graphs, linked lists)
  • Builder patterns for fluent APIs
  • Optional configuration with nil pointers
  • Object pools for recycling allocations
  • Interacting with C libraries via pointers

Patterns Quiz

How can pointers represent optional fields?

  • By using zero values
  • Nil pointers indicate absence
  • With special wrapper types
0 Interaction
0 Views
Views
0 Likes
×
×
×
🍪 CookieConsent@Ptutorials:~

Welcome to Ptutorials

$ Allow cookies on this site ? (y/n)

top-home