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:
*Tis 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?
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?
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.Namevs(*p).Name) - Use value receivers for immutable operations
Structs Quiz
Why are pointer receivers common for struct methods?
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
unsafepackage available for special cases
Safety Quiz
Why doesn't Go allow pointer arithmetic?
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?
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?
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?