Background: Receiver methods were introduced in Go to allow the implementation of interfaces and provide encapsulation of behavior for custom types, similar to class methods in OOP languages.
The Problem: In Go, methods can be declared for a struct (or other types) in two ways: via value receiver or pointer receiver. Incorrect application of their differences can lead to non-obvious errors, as behavior depends on how the method is declared and how it is called (via variable or pointer).
The Solution:
A method with a value receiver copies the entire struct upon invocation, and changes made inside such a method do not affect the original object. A pointer receiver allows manipulation of the original object and making changes. Choosing the correct receiver is important for performance optimization and correct behavior.
Code example:
package main import "fmt" type Counter struct { Value int } func (c Counter) IncByValue() { // value receiver c.Value++ } func (c *Counter) IncByPointer() { // pointer receiver c.Value++ } func main() { c := Counter{} c.IncByValue() fmt.Println(c.Value) // Will print 0 c.IncByPointer() fmt.Println(c.Value) // Will print 1 }
Key features:
1. If a struct contains large fields (e.g., an array of [1000]int), which receiver is better to use for a method and why?
Answer: It is better to use a pointer receiver to avoid the cost of copying a large amount of data. A method with a value receiver will copy the entire object, which is inefficient.
2. Is a struct with a pointer receiver compatible with an interface that defines methods with a value receiver?
Answer: No. If the interface method is declared on a value, and the struct implements it only on a pointer — the compiler will not consider it compatible.
3. Can a method with a pointer receiver be called on a value variable (rather than on a pointer)?
Answer: Yes. Go implicitly takes the address (&struct), meaning the method will be invoked correctly.
c := Counter{} c.IncByPointer() // Go will call (&c).IncByPointer()
In a project, the struct is huge, but all methods are declared on the value (value receiver). Every call copies the entire object, which significantly impacts performance.
Pros: Simplicity, impossible to accidentally change the original object. Cons: High memory and CPU costs.
For a small struct without much state, methods are declared on value; for large ones — only on pointers. Methods that modify the object use pointers.
Pros: Memory savings, correct state modification. Cons: Need to keep track of interface compatibility and remember pointer passing specifics.