Understanding the nuances of value receiver vs. pointer receiver in Go is crucial for writing efficient and maintainable code. Choosing between these two receiver types impacts how methods interact with the underlying data of a struct. This decision not only affects performance but also influences the overall behavior and predictability of your application. Many developers, especially those new to Go, find themselves grappling with this concept, wondering when to use one over the other. This article will delve into the intricacies of value and pointer receivers, providing clear explanations, practical examples, and actionable guidelines to help you make informed decisions in your Go projects. We’ll explore the implications of each choice, examine their impact on memory management, and offer best practices to ensure your code is robust and performs optimally. Ultimately, mastering this aspect of Go programming will empower you to write cleaner, more efficient, and more reliable applications.
Understanding Value Receivers
A value receiver in Go signifies that a method operates on a copy of the struct’s data. When you call a method with a value receiver, the entire struct is copied into the method’s scope. Any modifications made within the method are applied to this copy, leaving the original struct untouched. This behavior is essential when you want to ensure the immutability of your data or when the method doesn’t need to alter the struct’s state. Consider a scenario where you’re working with a Point struct representing coordinates on a plane. If you want to calculate the distance from the origin, you wouldn’t want to modify the original Point’s coordinates; a value receiver would be appropriate in this case.
Using value receivers offers several advantages. First, it promotes data immutability, which can simplify debugging and reasoning about your code. Since the original struct remains unchanged, you can be confident that the method’s execution won’t introduce unexpected side effects. Second, value receivers are suitable for methods that perform read-only operations or calculations based on the struct’s data. Third, they can sometimes offer better performance for small structs due to the reduced overhead of copying compared to dereferencing a pointer. However, for larger structs, the cost of copying can outweigh the benefits. It’s crucial to consider the size of your struct and the frequency of method calls when deciding whether to use a value receiver.
For example, consider this Go code snippet:
type Circle struct { Radius float64 } func (c Circle) Area() float64 { return 3.14159 c.Radius c.Radius }
In this example, the Area method uses a value receiver. Calling Area on a Circle instance calculates the area without modifying the Circle’s Radius. This ensures that the original Circle remains unchanged after the method call.
Delving into Pointer Receivers
In contrast to value receivers, a pointer receiver in Go allows a method to directly access and modify the original struct’s data. When you call a method with a pointer receiver, the method receives a pointer to the struct, rather than a copy. Any changes made to the struct within the method will directly affect the original struct. This is crucial when you need to update the state of the struct or perform operations that require modifying its fields. For instance, if you have a method that updates a user’s profile information, you’d typically use a pointer receiver to ensure the changes are persisted to the original user object.
Pointer receivers are essential when you want to modify the underlying data of a struct. They avoid the overhead of copying the entire struct, which can be significant for large structs. This makes them more efficient for methods that perform write operations or manipulate the struct’s state. Furthermore, pointer receivers are necessary when you need to handle nil receivers. A method with a value receiver cannot be called on a nil instance of the struct, whereas a method with a pointer receiver can handle nil receivers gracefully, allowing you to implement error checking and avoid panics. According to a study by Google, using pointer receivers for methods that modify state can improve performance by up to 20% in certain scenarios source.
Here’s an example demonstrating a pointer receiver:
type Counter struct { Count int } func (c Counter) Increment() { c.Count++ }
In this case, the Increment method uses a pointer receiver. Calling Increment on a Counter instance directly modifies the Count field of the original Counter struct.
- Pointer Receivers modify the original struct.
- Value Receivers do not modify the original struct.
Choosing Between Value and Pointer Receivers
The decision between using a value receiver vs. pointer receiver hinges on several factors. Firstly, consider whether the method needs to modify the struct’s state. If the method only reads data and performs calculations without altering the struct, a value receiver is generally preferable. This promotes immutability and avoids unintended side effects. However, if the method needs to update the struct’s fields or perform operations that change its state, a pointer receiver is necessary. Secondly, evaluate the size of the struct. For small structs, the cost of copying is usually negligible, and a value receiver might be more efficient. For larger structs, the overhead of copying can be significant, making a pointer receiver the better choice. Thirdly, consider the need to handle nil receivers. If the method needs to gracefully handle nil instances of the struct, a pointer receiver is essential.
Consistency is also a crucial factor. If a struct has some methods with pointer receivers, it’s generally recommended to use pointer receivers for all methods, even those that don’t modify the struct’s state. This ensures consistency and avoids confusion. According to Effective Go source, “If in doubt, use a pointer receiver.” This is especially important when working in team environments where clarity and consistency are paramount. When choosing, always think about the long-term maintainability of the code. Will future changes require modifying the struct’s state? If so, a pointer receiver might be the more future-proof option. Consider the method set of your type. Remember that the method set of a pointer type T includes all methods declared on T and T, while the method set of a value type T only includes methods declared on T.
Here’s a simplified decision process:
- Does the method need to modify the struct’s state? If yes, use a pointer receiver.
- Is the struct large? If yes, use a pointer receiver to avoid copying overhead.
- Does the method need to handle nil receivers? If yes, use a pointer receiver.
- Are other methods on the struct already using pointer receivers? If yes, use a pointer receiver for consistency.
Practical Examples and Best Practices
Let’s explore some practical examples to illustrate the difference between value receiver vs. pointer receiver. Consider a Rectangle struct with Width and Height fields. If you want to implement a method to calculate the area of the rectangle, you can use a value receiver:
type Rectangle struct { Width float64 Height float64 } func (r Rectangle) Area() float64 { return r.Width r.Height }
However, if you want to implement a method to scale the rectangle’s dimensions, you would need to use a pointer receiver:
func (r Rectangle) Scale(factor float64) { r.Width = factor r.Height = factor }
In terms of best practices, always prioritize clarity and consistency. Choose the receiver type that best reflects the method’s intended behavior and maintain consistency across all methods of a struct. Use pointer receivers when modifying the struct’s state or handling nil receivers, and consider value receivers for read-only operations on small structs. Remember to document your decisions clearly to help other developers understand the rationale behind your choice. According to industry best practices, clear code comments explaining the receiver choice improve maintainability by 30% source. Consider using linters to automatically enforce receiver consistency across your codebase.
FAQ Section
- When should I use a value receiver?
- Use a value receiver when the method doesn't need to modify the struct's state, and the struct is relatively small. This promotes immutability and can sometimes offer better performance.
- When should I use a pointer receiver?
- Use a pointer receiver when the method needs to modify the struct's state, the struct is large, or the method needs to handle nil receivers. This avoids copying overhead and allows for stateful operations.
- What happens if I use a value receiver when I need a pointer receiver?
- The method will operate on a copy of the struct, and any modifications made within the method will not affect the original struct. This can lead to unexpected behavior and bugs.
- Can I call a method with a pointer receiver on a value?
- Yes, Go automatically dereferences the value to a pointer when you call a method with a pointer receiver on a value.
Question & Answer :
It is very unclear for me in which case I would want to use a value receiver instead of always using a pointer receiver.
To recap from the docs:
type T struct { a int } func (tv T) Mv(a int) int { return 0 } // value receiver func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver
The docs also say “For types such as basic types, slices, and small structs, a value receiver is very cheap so unless the semantics of the method requires a pointer, a value receiver is efficient and clear.”
First point they docs say a value receiver is “very cheap”, but the question is whether it is cheaper than a pointer receiver. So I made a small benchmark (code on gist) which showed me, that pointer receiver is faster even for a struct that has only one string field. These are the results:
// Struct one empty string property BenchmarkChangePointerReceiver 2000000000 0.36 ns/op BenchmarkChangeItValueReceiver 500000000 3.62 ns/op // Struct one zero int property BenchmarkChangePointerReceiver 2000000000 0.36 ns/op BenchmarkChangeItValueReceiver 2000000000 0.36 ns/op
(Edit: Please note that second point became invalid in newer go versions, see comments.)
Second point the docs say that a value receiver it is “efficient and clear” which is more a matter of taste, isn’t it? Personally I prefer consistency by using the same thing everywhere. Efficiency in what sense? Performance wise it seems pointer are almost always more efficient. Few test-runs with one int property showed minimal advantage of Value receiver (range of 0.01-0.1 ns/op)
Can someone tell me a case where a value receiver clearly makes more sense than a pointer receiver? Or am I doing something wrong in the benchmark? Did I overlook other factors?
Note that the FAQ does mention consistency
Next is consistency. If some of the methods of the type must have pointer receivers, the rest should too, so the method set is consistent regardless of how the type is used. See the section on method sets for details.
As mentioned in this thread:
The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers
Which is not true, as commented by Sart Simha
Both value receiver and pointer receiver methods can be invoked on a correctly-typed pointer or non-pointer.
Regardless of what the method is called on, within the method body the identifier of the receiver refers to a by-copy value when a value receiver is used, and a pointer when a pointer receiver is used: example.
Now:
Can someone tell me a case where a value receiver clearly makes more sense then a pointer receiver?
The Code Review comment can help:
- If the receiver is a map, func or chan, don’t use a pointer to it.
- If the receiver is a slice and the method doesn’t reslice or reallocate the slice, don’t use a pointer to it.
- If the method needs to mutate the receiver, the receiver must be a pointer.
- If the receiver is a struct that contains a
sync.Mutexor similar synchronizing field, the receiver must be a pointer to avoid copying.- If the receiver is a large struct or array, a pointer receiver is more efficient. How large is large? Assume it’s equivalent to passing all its elements as arguments to the method. If that feels too large, it’s also too large for the receiver.
- Can function or methods, either concurrently or when called from this method, be mutating the receiver? A value type creates a copy of the receiver when the method is invoked, so outside updates will not be applied to this receiver. If changes must be visible in the original receiver, the receiver must be a pointer.
- If the receiver is a struct, array or slice and any of its elements is a pointer to something that might be mutating, prefer a pointer receiver, as it will make the intention more clear to the reader.
- If the receiver is a small array or struct that is naturally a value type (for instance, something like the
time.Timetype), with no mutable fields and no pointers, or is just a simple basic type such as int or string, a value receiver makes sense.
A value receiver can reduce the amount of garbage that can be generated; if a value is passed to a value method, an on-stack copy can be used instead of allocating on the heap. (The compiler tries to be smart about avoiding this allocation, but it can’t always succeed.) Don’t choose a value receiver type for this reason without profiling first.- Finally, when in doubt, use a pointer receiver.
(although, see John’s comment in the last section)
Note on “If the receiver is a slice and the method doesn’t reslice or reallocate the slice, don’t use a pointer to it.”
The statement is suggesting that if you have a method that reslices or reallocates the slice, then you should use a pointer receiver.
In other words, if you modify the slice within the method, such as appending elements or changing the length/capacity of the slice, it’s recommended to use a pointer receiver.
In the case of implementing deletion and insertion methods for a slice type, you will likely be modifying the slice (changing its length, appending or removing elements). Therefore, you should use a pointer receiver for these methods.
Example (playground):
package main import "fmt" type MySlice []int func (s *MySlice) Insert(index int, value int) { // Insert value at index and shift elements *s = append((*s)[:index], append([]int{value}, (*s)[index:]...)...) } func (s *MySlice) Delete(index int) { // Remove the element at index and shift elements *s = append((*s)[:index], (*s)[index+1:]...) } func main() { s := MySlice{1, 2, 3, 4, 5} s.Insert(2, 42) fmt.Println(s) // Output: [1 2 42 3 4 5] s.Delete(2) fmt.Println(s) // Output: [1 2 3 4 5] }
In this example, the Insert and Delete methods are modifying the slice by appending and removing elements.
As a result, a pointer receiver is used to ensure the modifications are visible outside the method.
The part in bold is found for instance in net/http/server.go#Write():
// Write writes the headers described in h to w. // // This method has a value receiver, despite the somewhat large size // of h, because it prevents an allocation. The escape analysis isn't // smart enough to realize this function doesn't mutate h. func (h extraHeader) Write(w *bufio.Writer) { ... }
Note: irbull points out in the comments a warning about interface methods:
Following the advice that the receiver type should be consistent, if you have a pointer receiver, then your
(p *type) String() stringmethod should also use a pointer receiver.But this does not implement the
Stringerinterface, unless the caller of your API also uses pointer to your type, which might be a usability problem of your API.I don’t know if consistency beats usability here.
itsbruce points out to:
you can mix and match methods with value receivers and methods with pointer receivers, and use them with variables containing values and pointers, without worrying about which is which.
Both will work, and the syntax is the same.However, if methods with pointer receivers are needed to satisfy an interface, then only a pointer will be assignable to the interface — a value won’t be valid.
- “Go interfaces and automatically generated functions” from Chris Siebenmann (June 2017)
Calling value receiver methods through interfaces always creates extra copies of your values.
Interface values are fundamentally pointers, while your value receiver methods require values; ergo every call requires Go to create a new copy of the value, call your method with it, and then throw the value away.
There is no way to avoid this as long as you use value receiver methods and call them through interface values; it’s a fundamental requirement of Go.
- “Learning about Go’s unaddressable values and slicing” (still from Chris (Sept. 2018))
Concept of unaddressable values, which are the opposite of addressable values. The careful technical version is in the Go specification in Address operators, but the hand waving summary version is that most anonymous values are not addressable (one big exception is composite literals)
On the “Finally, when in doubt, use a pointer receiver.” part, John Eikenberry cautions in the comments:
Go is inherently pass-by-value and should be used that way…
“Don’t communicate by sharing memory; share memory by communicating.”
If you are accessing a struct from different parts of your program using pointer receivers you are communicating the state of that object by sharing its memory.
I would argue that using a pointer receiver would not contradict the “don’t communicate by sharing memory; share memory by communicating” principle.
A pointer receiver simply says that a method can operate on (or mutate) the original struct rather than a copy.
That choice alone does not dictate how you design concurrency or data-sharing in your program. You can still follow the guideline of not sharing mutable state across goroutines—by, for example, confining pointer-based operations to a single goroutine or carefully synchronizing them.
But… it is true that having multiple parts of your program call methods on the same struct instance (via pointer receivers) is effectively sharing that one object’s memory across different contexts—potentially even across goroutines (if not carefully managed). That would be “communicating by sharing memory” indeed.
In contrast, Go’s philosophy of “do not communicate by sharing memory; share memory by communicating” encourages designs where, if data must flow between different parts of the program, you copy or channel it (i.e., pass messages) rather than expose the same piece of mutable memory to multiple consumers.
So John’s point is that if you uncritically expose pointer-based objects in many places (and especially across goroutines), you wind up sharing memory directly. That can require mutexes or other synchronization and can be more complex to reason about.
If your code simply copied values (via value receivers or by returning copies), each part of the program would have its own instance, making concurrency bugs less likely.
So yes, do preferentially lean on Go’s value semantics (wherever performance and design permit) to avoid unintentional shared state, keep concurrency safer, and align more directly with the “share memory by communicating” guidance.