Value Receivers — Find the Bug
Bug 1 — Mutation does not work
type C struct{ n int }
func (c C) Inc() { c.n++ }
c := C{}
c.Inc()
fmt.Println(c.n) // ?
Bug: Value receiver — the copy is mutated. Output: 0. Fix: Pointer receiver, or return: func (c C) Inc() C { c.n++; return c }.
Bug 2 — Mutex value receiver
type X struct{ mu sync.Mutex; n int }
func (x X) Inc() { x.mu.Lock(); defer x.mu.Unlock(); x.n++ }
Bug: The mutex is copied on every call. There is no synchronization. Fix: Pointer receiver.
Bug 3 — Slice append is local
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: append mutates the local slice header. Output: []. Fix: Pointer receiver, or return.
Bug 4 — Slice index does take effect
type Box struct{ items []int }
func (b Box) ZeroFirst() { b.items[0] = 0 }
box := Box{items: []int{1, 2, 3}}
box.ZeroFirst()
fmt.Println(box.items) // ?
Not a bug, but a nuance: Output [0 2 3]. The slice header is copied, but the underlying array is shared.
Bug 5 — Map mutation
type Cache struct{ m map[string]string }
func (c Cache) Set(k, v string) { c.m[k] = v }
c := Cache{m: map[string]string{}}
c.Set("a", "1")
fmt.Println(c.m)
Not a bug: The map header is copied, but the underlying map is shared. Output: map[a:1].
Bug 6 — Comparison error
type S struct{ items []int }
a := S{items: []int{1}}
b := S{items: []int{1}}
fmt.Println(a == b)
Bug: Compile error. S is not comparable (slice field). Fix: Custom Equal method: func (a S) Equal(b S) bool { ... }
Bug 7 — Map key non-comparable
type S struct{ items []int }
m := map[S]int{}
Bug: Compile error: invalid map key type S. Fix: Make S comparable, or use an ID as the map key.
Bug 8 — Nil interface vs nil concrete
type MyErr struct{ msg string }
func (e MyErr) Error() string { return e.msg }
func doIt() error {
var e *MyErr // nil
return e
}
err := doIt()
fmt.Println(err == nil) // ?
Bug: Output false. The interface value contains (*MyErr, nil) — the type is set, the value is nil. The interface value itself is not nil. Fix: func doIt() error {
var e *MyErr
if e == nil { return nil }
return e
}
Bug 9 — Discarding the wither result
type Config struct{ port int }
func (c Config) WithPort(p int) Config { c.port = p; return c }
cfg := Config{}
cfg.WithPort(8080) // result ignored
fmt.Println(cfg.port) // ?
Bug: Output 0. The wither method's return value was discarded. Fix: cfg = cfg.WithPort(8080).
Bug 10 — Mutation through a pointer field
type Holder struct{ p *int }
func (h Holder) Zero() { *h.p = 0 }
n := 5
h := Holder{p: &n}
h.Zero()
fmt.Println(n)
Not a bug: Output 0. Through the pointer field, the original n is affected.
Bug 11 — Array vs slice
type S struct{ data [3]int }
func (s S) Modify() { s.data[0] = 99 }
s := S{data: [3]int{1, 2, 3}}
s.Modify()
fmt.Println(s.data)
Bug: Arrays have value semantics (NOT slices). Output: [1 2 3] (no effect). Fix: Pointer receiver.
Bug 12 — Mixed receiver method set
type S struct{}
func (s S) Read() {}
func (s *S) Write() {}
type RW interface { Read(); Write() }
var _ RW = S{}
Bug: Compile error. S's method set does not include Write (pointer receiver). Fix: var _ RW = &S{}.
Bug 13 — Loop variable + value method
items := []Item{{1}, {2}}
fns := []func(){}
for _, item := range items {
fns = append(fns, func() { fmt.Println(item.id) })
}
for _, f := range fns { f() }
Bug (Go 1.21 and earlier): item is the same variable across iterations. The closures all point to the last item. Output: 2 2. Go 1.22+: Output 1 2.
Bug 14 — Equality on float
a := Vec2{0.1 + 0.2, 0}
b := Vec2{0.3, 0}
fmt.Println(a == b)
Bug: Float arithmetic — 0.1 + 0.2 != 0.3 exactly. Output: false. Fix: Approximate equality method.
Bug 15 — String concatenation in method
type Logger struct{ prefix string }
func (l Logger) Log(msg string) {
l.prefix += " " // mutates locally
fmt.Println(l.prefix + msg)
}
Not a bug, but a nuance: prefix is mutated locally — the original l.prefix is not changed across calls. OK in this context.
Bug 16 — Incorrect interface usage
type Stringer interface { String() string }
type S struct{}
func (s S) String() string { return "S" }
var x interface{} = S{}
y := x.(Stringer) // OK
fmt.Println(y.String())
Not a bug: The type assertion works. S's method set contains String.
Bug 17 — Returning sliced data
type Box struct{ items []int }
func (b Box) GetItems() []int { return b.items }
box := Box{items: []int{1, 2, 3}}
items := box.GetItems()
items[0] = 99
fmt.Println(box.items) // ?
Bug: Output [99 2 3]. The slice is shared. Fix: Defensive copy: func (b Box) GetItems() []int {
out := make([]int, len(b.items))
copy(out, b.items)
return out
}
Bug 18 — Time addition
t := time.Now()
t.Add(time.Hour)
fmt.Println(t) // one hour later?
Bug: Add is a value receiver — it returns a new value. The original t is not changed. Output: original time. Fix: t = t.Add(time.Hour).
Bug 19 — Nil receiver in value method
type S struct{ n int }
func (s S) M() int { return s.n }
var p *S = nil
fmt.Println(p.M()) // ?
Bug: Panic. Value receiver — Go performs (*p).M(). *p is a nil dereference. Fix: Pointer receiver with a nil check.
Bug 20 — Hash on slice
type S struct{ items []int }
m := map[S]int{} // ?
Bug: Compile error. S is not comparable. Fix: Create a comparable type, or use an ID as the key.