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:
- Reference semantics for efficient data passing
- Mutation through pointer receivers
- Memory efficiency by avoiding copies
- Flexibility in function design
- Control over memory allocation
Understanding pointers is crucial for writing idiomatic Go code.
Comments