Learning Go: Method - Pointer Receiver vs Value Receiver
When writing methods in Go, one of the fundamental decisions you'll make is whether to use a pointer receiver or a value receiver. This choice impacts how your methods interact with data, affecting both performance and behavior.
What Are Methods in Go?
In Go, a method is a function with a special receiver argument. The receiver appears between the func keyword and the method name:
type Person struct {
Name string
Age int
}
// Method with value receiver
func (p Person) GetName() string {
return p.Name
}
// Method with pointer receiver
func (p *Person) SetName(name string) {
p.Name = name
}Value Receiver
A value receiver creates a copy of the struct when the method is called. Any modifications made inside the method don't affect the original struct.
type Counter struct {
Count int
}
func (c Counter) Increment() {
c.Count++ // This only modifies the copy!
}
func main() {
counter := Counter{Count: 0}
counter.Increment()
fmt.Println(counter.Count) // Output: 0 (unchanged!)
}When to Use Value Receivers
- The method doesn't modify the receiver - When you only need to read data
- Small structs - Copying is cheap for small data structures
- Immutability - When you want to ensure the original data remains unchanged
- Simple types - For built-in types like
int,string, etc.
type Point struct {
X, Y int
}
// Good use of value receiver - just reading values
func (p Point) Distance() float64 {
return math.Sqrt(float64(p.X*p.X + p.Y*p.Y))
}Pointer Receiver
A pointer receiver passes a reference to the struct. The method can modify the original struct, and no copying occurs.
type Counter struct {
Count int
}
func (c *Counter) Increment() {
c.Count++ // This modifies the original!
}
func main() {
counter := Counter{Count: 0}
counter.Increment()
fmt.Println(counter.Count) // Output: 1 (modified!)
}When to Use Pointer Receivers
- The method modifies the receiver - When you need to change the struct's state
- Large structs - Avoid expensive copy operations
- Consistency - If some methods need pointers, use pointers for all methods on that type
- Mutexes or other sync primitives - These must not be copied
type Account struct {
Balance float64
mu sync.Mutex
}
// Must use pointer receiver - modifies state and contains mutex
func (a *Account) Deposit(amount float64) {
a.mu.Lock()
defer a.mu.Unlock()
a.Balance += amount
}
func (a *Account) GetBalance() float64 {
a.mu.Lock()
defer a.mu.Unlock()
return a.Balance
}Key Differences
| Aspect | Value Receiver | Pointer Receiver |
|---|---|---|
| Modification | Cannot modify original | Can modify original |
| Memory | Creates a copy | Uses reference |
| Performance | Slower for large structs | Faster for large structs |
| nil safety | Cannot be called on nil | Can be called on nil |
| Thread safety | Safer (immutable) | Requires synchronization |
Important Gotcha: nil Pointer Receivers
Pointer receivers can be called even when the receiver is nil. You need to handle this explicitly:
type Tree struct {
Value int
Left *Tree
Right *Tree
}
func (t *Tree) Insert(value int) {
if t == nil {
// Cannot modify nil receiver!
// This is a common mistake
return
}
// ... insertion logic
}Best Practices
- Be consistent - If any method needs a pointer receiver, use pointer receivers for all methods on that type
- Consider the semantics - Does your type represent a value (like
time.Time) or a resource (likeos.File)? - Document your choice - Make it clear in comments whether a type should be copied or referenced
- Default to pointer receivers - When in doubt, pointer receivers are more flexible
// Good: Consistent pointer receivers
type User struct {
ID int
Name string
}
func (u *User) SetName(name string) {
u.Name = name
}
func (u *User) GetID() int {
return u.ID // Still uses pointer receiver for consistency
}Real-World Example
Here's a practical example showing both approaches:
package main
import "fmt"
// Value type - immutable point
type Point struct {
X, Y float64
}
// Value receiver - returns new Point
func (p Point) Add(other Point) Point {
return Point{X: p.X + other.X, Y: p.Y + other.Y}
}
// Reference type - mutable rectangle
type Rectangle struct {
TopLeft Point
BottomRight Point
}
// Pointer receiver - modifies in place
func (r *Rectangle) Move(dx, dy float64) {
r.TopLeft.X += dx
r.TopLeft.Y += dy
r.BottomRight.X += dx
r.BottomRight.Y += dy
}
// Pointer receiver - calculates area (consistent with Move)
func (r *Rectangle) Area() float64 {
width := r.BottomRight.X - r.TopLeft.X
height := r.BottomRight.Y - r.TopLeft.Y
return width * height
}
func main() {
p1 := Point{X: 1, Y: 2}
p2 := Point{X: 3, Y: 4}
p3 := p1.Add(p2)
fmt.Printf("Sum: (%v, %v)\n", p3.X, p3.Y)
rect := Rectangle{
TopLeft: Point{X: 0, Y: 0},
BottomRight: Point{X: 10, Y: 10},
}
fmt.Printf("Area: %v\n", rect.Area())
rect.Move(5, 5)
fmt.Printf("After move: %+v\n", rect)
}Conclusion
Choosing between pointer and value receivers is about understanding your data's semantics:
- Use value receivers for small, immutable types where copying is cheap
- Use pointer receivers for types that should be modified, large structs, or when consistency demands it
When in doubt, prefer pointer receivers—they're more flexible and prevent common bugs related to unintentional copying.
Happy coding in Go! 🚀
Related Posts
Go Learning Roadmap:
- Go Learning Roadmap - Complete series overview
- Phase 1: Go Fundamentals - Variables, types, functions, and pointers
- Go Goroutines and Concurrency - Concurrent programming
- Go Channels and Communication - Channel patterns
Other Go Posts:
- Never Use Arrays in Go - Why slices are almost always better
- Composition Over Inheritance - Go's approach to code reuse
References
- Learning Go: An Idiomatic Approach to Real-World Go Programming by Jon Bodner - An excellent resource for understanding Go's best practices and idiomatic patterns, including comprehensive coverage of methods and receivers.
📬 Subscribe to Newsletter
Get the latest blog posts delivered to your inbox every week. No spam, unsubscribe anytime.
We respect your privacy. Unsubscribe at any time.
💬 Comments
Sign in to leave a comment
We'll never post without your permission.