Go linter that detects mixing pointer and value method receivers.
Mixing pointer and value receivers on the same type creates subtle, hard-to-detect bugs that can cause:
When you copy a struct with a mutex, you copy the mutex state, leading to race conditions:
type RPC struct {
mu sync.Mutex // This is the problem!
result int
done chan struct{}
}
func (rpc *RPC) compute() {
rpc.mu.Lock()
defer rpc.mu.Unlock()
rpc.result = 42
}
func (RPC) version() int { // Value receiver copies the mutex!
return 1
}
func main() {
rpc := &RPC{done: make(chan struct{})}
go rpc.compute() // Locks original mutex
version := rpc.version() // Uses copied mutex - RACE!
// ...
}
Value receivers create copies, so modifications are lost:
type Counter struct {
value int
}
func (c *Counter) Increment() { c.value++ } // pointer receiver
func (c Counter) Reset() { c.value = 0 } // value receiver - NO EFFECT!
Mixed receivers make code behavior unpredictable and harder to reason about.
Consistency is key: Go's official guidance says Don't mix receiver types. Choose either pointers or values for all methods on a type.
recvcheck
automatically detects these issues before they reach production.
# Standalone
go install github.com/raeperd/recvcheck/cmd/recvcheck@latest
# With golangci-lint (recommended)
# Add to .golangci.yml:
linters:
enable:
- recvcheck
recvcheck ./...
# or
golangci-lint run
Output:
main.go:8:1: the methods of "RPC" use pointer receiver and non-pointer receiver
# .golangci.yml
linters-settings:
recvcheck:
# Disable default exclusions (MarshalJSON, etc.)
disable-builtin: false
# Custom exclusions
exclusions:
- "Server.Shutdown" # Specific method
- "*.String" # All String methods
Marshal methods are excluded by default as they commonly use value receivers:
*.MarshalText
,*.MarshalJSON
,*.MarshalYAML
*.MarshalXML
,*.MarshalBinary
,*.GobEncode
❌ Bad - Mixed receivers:
func (u *User) SetName(name string) { } // pointer
func (u User) GetName() string { } // value - inconsistent!
✅ Good - Consistent receivers:
func (u *User) SetName(name string) { } // pointer
func (u *User) GetName() string { } // pointer - consistent!
make test # Run tests
make lint # Run linter
make build # Build binary
MIT