Pointer Receivers — Find the Bug¶
Bug 1 — Mutation isn't working¶
Bug: Value receiver — the copy is mutated. Output: 0.
Fix: func (c *C) Inc().
Bug 2 — Map element¶
Bug: Compile error. Map element is not addressable.
Fix:
Bug 3 — Mutex value receiver¶
Bug: Value receiver — the mutex and n are copied on every call. No synchronization. Race condition.
Fix: func (x *X) Inc().
Bug 4 — Interface mismatch¶
type Greeter interface { Greet() string }
type P struct{ name string }
func (p *P) Greet() string { return "hi " + p.name }
var g Greeter = P{name: "Alice"}
Bug: Compile error. Greet has a pointer receiver — it's not in P's method set. *P is required.
Fix: var g Greeter = &P{name: "Alice"}.
Bug 5 — Nil panic¶
type L struct{ prefix string }
func (l *L) Log(msg string) { fmt.Println(l.prefix, msg) }
var l *L
l.Log("hi")
Bug: l == nil — l.prefix panics.
Fix:
Bug 6 — Slice mutation¶
type S struct{ items []int }
func (s S) Add(x int) { s.items = append(s.items, x) }
s := S{}
s.Add(1); s.Add(2)
fmt.Println(s.items) // ?
Bug: Value receiver — s is a copy. append affects the local slice. Output: [].
Fix: func (s *S) Add(x int).
Bug 7 — Pointer escape¶
Not a bug, but a nuance: c escapes to the heap (returning a pointer). This is OK, but the user should know. go build -gcflags='-m' will print a notice.
Bug 8 — Method value loop¶
services := []*Service{{1}, {2}, {3}}
callbacks := []func(){}
for _, s := range services {
callbacks = append(callbacks, s.Process)
}
for _, cb := range callbacks { cb() }
Bug (Go 1.21 and earlier): s is the same on each iteration. The method value retains the s pointer — all callbacks bind to the last service.
Fix (Go 1.21):
Go 1.22+ — this bug is gone.
Bug 9 — DB Close local¶
type DB struct{ conn *sql.DB }
func (d DB) Close() { d.conn.Close(); d.conn = nil }
db := DB{conn: openDB()}
db.Close()
db.conn.Ping() // ?
Bug: Close has a value receiver — d.conn = nil only affects the local copy. db.conn still exists (closed). Ping returns "use of closed connection".
Fix: func (d *DB) Close().
Bug 10 — Pointer to interface¶
Bug: Compile error. You can't add a method to an interface type (or to its pointer).
Fix: Add the method to a concrete type.
Bug 11 — Embedded mutex copy¶
Bug: When s1 is copied, Base is too — the mutex is copied. go vet says "passes lock by value".
Fix: Pointer embed — type S struct { *Base } or a noCopy marker.
Bug 12 — Mixed receivers¶
type Buffer struct{ data []byte }
func (b Buffer) Len() int { return len(b.data) }
func (b *Buffer) Write(p []byte) { b.data = append(b.data, p...) }
Not a bug, but bad design: The method set is inconsistent. The caller can get confused.
Fix: All pointer (Buffer is stateful):
Bug 13 — Variadic interface satisfaction¶
type Writer interface { Write(p []byte) (int, error) }
type B struct{}
func (b B) Write(p []byte) (int, error) { return len(p), nil }
func main() {
var w Writer = B{} // ?
w.Write([]byte("hi"))
}
Not a bug: Value receiver — Write is in B's method set — OK.
But using *B is conventional:
Bug 14 — Constructor return value¶
Not a bug, but: Constructors usually return *T, so the caller is ready if mutation is needed in the future:
Bug 15 — Method value escape¶
Not a bug: s escapes to the heap — this is necessary because the callback must hold onto s. go build -gcflags='-m' shows: "s.Handle escapes to heap".
Bug 16 — Receiver shadowing¶
Bug: The inner c is a copy. c.n++ only mutates the copy.
Fix: Don't shadow:
Bug 17 — Goroutine race¶
type Counter struct{ n int }
func (c *Counter) Inc() { c.n++ }
c := &Counter{}
for i := 0; i < 100; i++ { go c.Inc() }
time.Sleep(time.Second)
fmt.Println(c.n)
Bug: Race condition — c.n++ is not atomic. go run -race will catch it.
Fix: Mutex or atomic:
Bug 18 — & in the wrong place¶
type C struct{ n int }
func (c *C) Inc() { c.n++ }
func process(c C) {
(&c).Inc() // ?
}
c := C{}
process(c)
fmt.Println(c.n) // ?
Bug: process accepts c by value — a copy. &c is the address of the local copy. The original c doesn't change. Output: 0.
Fix: Have process accept c *C or return the value.
Bug 19 — Pointer to local in goroutine¶
func startWorker() {
counter := 0
go func() {
for i := 0; i < 100; i++ { counter++ }
}()
fmt.Println(counter)
}
Bug: Race condition — the goroutine reads and writes counter, and so does the main goroutine. No synchronization.
Fix: Channel or atomic.
Bug 20 — Method on alias type¶
Bug: time.Time is in another package. You can't add methods to an alias. Compile error.
Fix: Defined type: