Pointers in Go
31 January 2025 / 5 min read
Pointers are a fundamental concept in Go (Golang) that allow you to work directly with memory addresses. They are powerful but can be tricky if not understood properly. Below is a detailed explanation of everything you need to know about pointers in Go.
What is a Pointer?
A pointer is a variable that stores the memory address of a value. Instead of holding the actual value, a pointer holds the location where the value is stored in memory.
Every variable in Go is stored at a specific memory location. A pointer points to this location.
Declaring Pointers
In Go, you declare a pointer using the *
symbol followed by the type of the variable it points to.
var p *int // p is a pointer to an int
Here, p
is a pointer to an integer. Initially, it holds the zero value for pointers, which is nil
.
Getting the Address of a Variable
You can get the memory address of a variable using the &
operator.
x := 42
p := &x // p now holds the memory address of x
&x
gives the address ofx
.p
is now a pointer tox
.
Dereferencing a Pointer
Dereferencing in Go means accessing the value stored at the memory address a pointer holds.
To access the value stored at the memory address a pointer holds, use the *
operator.
fmt.Println(*p) // Prints 42 (the value of x)
*p
retrieves the value stored at the memory addressp
points to.
Pointer Zero Value
The zero value of a pointer is nil
. A nil
pointer does not point to any memory location.
var p *int
fmt.Println(p) // Prints <nil>
Attempting to dereference a nil
pointer will cause a runtime panic.
Pointer Arithmetic
Go does not support pointer arithmetic (unlike C/C++). You cannot increment or decrement pointers to navigate through memory.
Pointers and Functions
Pointers are often used in functions to modify the original value of a variable.
Pass by Value
By default, Go passes arguments to functions by value (a copy is made).
func modifyValue(val int) {
val = 100
}
x := 42
modifyValue(x)
fmt.Println(x) // Prints 42 (unchanged)
Pass by Reference
To modify the original variable, pass a pointer to the function.
func modifyPointer(val *int) {
*val = 100
}
x := 42
modifyPointer(&x)
fmt.Println(x) // Prints 100 (changed)
Pointers and Structs
Pointers are commonly used with structs to avoid copying large structs and to modify struct fields.
type Person struct {
Name string
Age int
}
func (p *Person) UpdateAge(newAge int) {
p.Age = newAge
}
person := Person{Name: "Mark", Age: 30}
person.UpdateAge(31)
fmt.Println(person.Age) // Prints 31
- Using a pointer receiver (
*Person
) allows the method to modify the original struct.
Pointer to a Pointer
You can have a pointer that points to another pointer.
x := 42
p := &x
pp := &p
fmt.Println(*pp) // Prints the address of x
fmt.Println(**pp) // Prints 42
pp
is a pointer to a pointer to an integer.
Pointers and Slices
Slices in Go are reference types, meaning they already contain a pointer to an underlying array. You rarely need to use pointers with slices.
slice := []int{1, 2, 3}
p := &slice
fmt.Println((*p)[0]) // Prints 1
Pointers and Maps
Like slices, maps are reference types. You don’t typically need pointers to modify maps.
m := map[string]int{"foo": 1}
p := &m
fmt.Println((*p)["foo"]) // Prints 1
Pointers and Interfaces
When working with interfaces, pointers can be used to satisfy the interface.
type Speaker interface {
Speak() string
}
type Dog struct{}
func (d *Dog) Speak() string {
return "Woof!"
}
var s Speaker = &Dog{}
fmt.Println(s.Speak()) // Prints "Woof!"
- The
Dog
type implements theSpeaker
interface using a pointer receiver.
Common Pitfalls with Pointers
-
Dereferencing a
nil
Pointer: This will cause a runtime panic.var p *int fmt.Println(*p) // Panic: nil pointer dereference
-
Memory Leaks: If you hold onto pointers unnecessarily, the garbage collector cannot free the memory.
-
Uninitialized Pointers: Always initialize pointers before using them.
Best Practices
- Use pointers when you need to modify the original variable.
- Avoid pointers for small or immutable data types (e.g.,
int
,string
). - Prefer passing structs by pointer to avoid copying large structs.
- Be cautious with
nil
pointers to avoid runtime panics.
Pointer Safety
Go’s garbage collector ensures that pointers are safe to use as long as they point to valid memory.
Example: Swapping Values Using Pointers
Here’s a classic example of swapping two values using pointers.
func swap(a, b *int) {
*a, *b = *b, *a
}
x, y := 10, 20
swap(&x, &y)
fmt.Println(x, y) // Prints 20, 10
Summary
- A pointer holds the memory address of a value.
- Use
&
to get the address of a variable and*
to dereference a pointer. - Pointers are useful for modifying variables outside a function’s scope and for working with large data structures.
- Be cautious with
nil
pointers and uninitialized pointers.
Frequently Asked Questions
-
Why use pointers?
- To modify the original variable.
- To avoid copying large data structures.
- To share data efficiently.
-
When not to use pointers?
- For small or immutable data types.
- When you don’t need to modify the original variable.
-
Are pointers safe in Go?
- Yes, Go’s garbage collector ensures memory safety, but you must avoid
nil
pointer dereferencing.
- Yes, Go’s garbage collector ensures memory safety, but you must avoid
Code Examples
Example 1: Basic Pointer Usage
package main
import "fmt"
func main() {
x := 42
p := &x
fmt.Println(*p) // Prints 42
}
Example 2: Modifying a Struct Using a Pointer
package main
import "fmt"
type Person struct {
Name string
Age int
}
func (p *Person) Birthday() {
p.Age++
}
func main() {
person := Person{Name: "Alice", Age: 30}
person.Birthday()
fmt.Println(person.Age) // Prints 31
}
Conclusion
Pointers are a powerful feature in Go that allow you to work directly with memory addresses. They are essential for efficient memory management and modifying data outside a function’s scope. However, they require careful handling to avoid common pitfalls like nil
pointer dereferencing.
By understanding and using pointers effectively, you can write more efficient and flexible Go programs.