Pointers in Go
Pointers are a fundamental concept in Go that allow you to work directly with memory addresses. Understanding pointers is crucial for efficient programming, especially when dealing with large data structures or when you need to modify values in functions. This tutorial covers pointer basics, syntax, and common use cases in Go.
What are Pointers?
A pointer is a variable that stores the memory address of another variable. Instead of holding a value directly, it “points to” where the value is stored in memory.
var x int = 42 // x holds the value 42
var p *int = &x // p holds the address of x
fmt.Println(p) // Prints memory address, like 0xc0000140b0
fmt.Println(*p) // Prints 42 (dereferencing)
Pointer Declaration and Initialization
Declaring Pointers
var p *int // Pointer to int, initially nil
var s *string // Pointer to string
var arr *[3]int // Pointer to array
Initializing Pointers
// Method 1: Using the address-of operator &
x := 42
p := &x
// Method 2: Using new() function
p2 := new(int)
*p2 = 100
// Method 3: Pointer to struct
type Person struct {
name string
age int
}
person := Person{"Alice", 30}
ptr := &personDereferencing Pointers
Dereferencing means accessing the value that the pointer points to, using the * operator.
x := 42
p := &x
fmt.Println(x) // 42
fmt.Println(*p) // 42 (same value)
*p = 100 // Change value through pointer
fmt.Println(x) // 100 (x was modified)
Pointers and Functions
Passing by Value (Default)
func modifyValue(x int) {
x = 100 // This only changes the local copy
}
func main() {
x := 42
modifyValue(x)
fmt.Println(x) // Still 42
}Passing by Reference (Using Pointers)
func modifyValue(ptr *int) {
*ptr = 100 // This changes the original value
}
func main() {
x := 42
modifyValue(&x)
fmt.Println(x) // Now 100
}Pointers with Different Types
Pointers to Slices
func modifySlice(s *[]int) {
*s = append(*s, 4, 5, 6)
}
func main() {
numbers := []int{1, 2, 3}
modifySlice(&numbers)
fmt.Println(numbers) // [1 2 3 4 5 6]
}Pointers to Maps
func addEntry(m *map[string]int, key string, value int) {
(*m)[key] = value
}
func main() {
scores := make(map[string]int)
addEntry(&scores, "Alice", 95)
fmt.Println(scores) // map[Alice:95]
}Pointers to Structs
type Rectangle struct {
width, height float64
}
func (r *Rectangle) scale(factor float64) {
r.width *= factor
r.height *= factor
}
func main() {
rect := Rectangle{10, 20}
rect.scale(2)
fmt.Println(rect) // {20 30}
}Nil Pointers
Pointers that don’t point to anything are nil.
var p *int
fmt.Println(p) // <nil>
fmt.Println(p == nil) // true
// Dereferencing nil pointer causes panic
// fmt.Println(*p) // Runtime panic!
// Safe dereferencing
if p != nil {
fmt.Println(*p)
}Pointer Arithmetic (Not Allowed in Go)
Unlike C/C++, Go doesn’t allow pointer arithmetic for safety reasons.
// This doesn't work in Go
p := &x
p++ // Compiler error!
The new() Function
new() allocates memory and returns a pointer to the zero value of the type.
p := new(int) // Equivalent to var p *int = new(int)
fmt.Println(*p) // 0
s := new(string) // Pointer to empty string
fmt.Println(*s) // ""
arr := new([3]int) // Pointer to zero-valued array
fmt.Println(*arr) // [0 0 0]
Pointer Receivers in Methods
When to use pointer receivers vs value receivers.
type Counter struct {
count int
}
// Value receiver - doesn't modify original
func (c Counter) increment() {
c.count++
}
// Pointer receiver - modifies original
func (c *Counter) incrementPtr() {
c.count++
}
func main() {
c := Counter{0}
c.increment()
fmt.Println(c.count) // 0 (unchanged)
c.incrementPtr()
fmt.Println(c.count) // 1 (changed)
}Common Patterns
Returning Pointers from Functions
func createPerson(name string, age int) *Person {
return &Person{name: name, age: age}
}
func main() {
p := createPerson("Bob", 25)
fmt.Println(p.name) // Bob
}Avoiding Large Struct Copies
type LargeStruct struct {
data [1000]int
}
// Pass by value (expensive)
func processLargeStruct(s LargeStruct) {
// Function gets a copy of the entire struct
}
// Pass by pointer (efficient)
func processLargeStructPtr(s *LargeStruct) {
// Function only gets the pointer
}Linked List Implementation
type Node struct {
value int
next *Node
}
func main() {
// Create nodes
head := &Node{value: 1}
head.next = &Node{value: 2}
head.next.next = &Node{value: 3}
// Traverse
current := head
for current != nil {
fmt.Println(current.value)
current = current.next
}
}Memory Management
Go has automatic garbage collection, so you don’t need to manually free memory like in C/C++.
func createData() *[]int {
data := []int{1, 2, 3}
return &data // Pointer to local variable is OK
}
func main() {
p := createData()
fmt.Println(*p) // [1 2 3] - data still accessible
}Common Mistakes
Forgetting to Dereference
x := 42
p := &x
fmt.Println(p) // Prints address
fmt.Println(*p) // Prints 42
Modifying Copies
type Config struct {
debug bool
}
func enableDebug(c Config) { // Value parameter
c.debug = true
}
func main() {
c := Config{debug: false}
enableDebug(c)
fmt.Println(c.debug) // false (unchanged)
}Returning Pointers to Local Variables
func badFunction() *int {
x := 42
return &x // Don't do this!
}
func goodFunction() *int {
x := 42
return &x // This is actually OK in Go
}Best Practices
- Use pointers when you need to modify the original value in functions
- Use pointers for large structs to avoid expensive copies
- Use value receivers for small structs or when you don’t need to modify
- Check for nil pointers before dereferencing
- Be consistent with pointer vs value receivers in methods
- Document when functions expect pointers in comments
Complete Example
package main
import "fmt"
type BankAccount struct {
owner string
balance float64
}
func (b *BankAccount) deposit(amount float64) {
if amount > 0 {
b.balance += amount
fmt.Printf("Deposited $%.2f. New balance: $%.2f\n", amount, b.balance)
}
}
func (b *BankAccount) withdraw(amount float64) bool {
if amount > 0 && amount <= b.balance {
b.balance -= amount
fmt.Printf("Withdrew $%.2f. New balance: $%.2f\n", amount, b.balance)
return true
}
fmt.Println("Insufficient funds or invalid amount")
return false
}
func (b *BankAccount) getBalance() float64 {
return b.balance
}
func createAccount(owner string, initialDeposit float64) *BankAccount {
account := &BankAccount{owner: owner, balance: 0}
account.deposit(initialDeposit)
return account
}
func main() {
// Create account
account := createAccount("Alice", 1000.0)
// Perform transactions
account.deposit(500.0)
account.withdraw(200.0)
account.withdraw(2000.0) // Should fail
fmt.Printf("Final balance for %s: $%.2f\n", account.owner, account.getBalance())
// Demonstrate pointer concepts
balancePtr := &account.balance
*balancePtr += 100 // Direct modification through pointer
fmt.Printf("After bonus: $%.2f\n", account.balance)
}Summary
Pointers in Go provide:
- Direct memory access for efficient operations
- Ability to modify values in functions
- Reduced copying for large data structures
- Method receivers for object-oriented programming
Key operators:
&- Address of (create pointer)*- Dereference (access value)
While pointers are powerful, Go’s design encourages using them judiciously, with value semantics being the default.
External Resources:
Related Tutorials: