Methods
What is a method?
Section titled “What is a method?”A method is a function with a receiver — an extra parameter that appears between the func keyword and the function name. The receiver binds the function to a named type.
func (r Rectangle) Area() float64 { return r.Width * r.Height}Here r Rectangle is the receiver. Area is now a method of Rectangle, callable as rect.Area().
Value receivers vs pointer receivers
Section titled “Value receivers vs pointer receivers”The receiver can be either a value or a pointer:
func (c Counter) Value() int { return c.count } // value receiverfunc (c *Counter) Inc() { c.count++ } // pointer receiverValue receiver (t T) | Pointer receiver (t *T) | |
|---|---|---|
| Sees | A copy of the value | The original value |
| Can mutate? | No — mutates the copy only | Yes |
| Callable on | Both T and *T | Both T (addressable) and *T |
| Use when | Read-only; small, cheap to copy | Mutation; large struct; consistent method set |
Be consistent: if any method on a type needs a pointer receiver, give all methods of that type pointer receivers. Mixing causes subtle method-set issues when the type is used as an interface value.
Methods on any named type
Section titled “Methods on any named type”Methods are not limited to structs. You can define methods on any named type in your package — except pointer types and interface types:
type Celsius float64
func (t Celsius) String() string { return fmt.Sprintf("%.1f°C", float64(t))}You cannot define a method on a type from another package — for example, func (t time.Time) Weekday2() string { ... } fails with cannot define new methods on non-local type time.Time — but wrapping it in a local named type is always allowed.
Method sets
Section titled “Method sets”The method set of a type determines which interfaces it satisfies:
- The method set of
Tcontains all methods with value receiver(t T). - The method set of
*Tcontains all methods — both(t T)and(t *T).
This is why an interface requiring a pointer-receiver method can only be satisfied by *T, not by T.
Addressability: when method sets bite
Section titled “Addressability: when method sets bite”Calling a pointer-receiver method on a value auto-takes its address — c.Inc() on a variable c really means (&c).Inc(). But Go can only take that address when c is addressable: a plain variable, a struct field of an addressable struct, or *p for some pointer p. Two everyday values are not addressable — a map value, and the result of a composite literal used directly.
type Counter struct{ count int }
func (c *Counter) Inc() { c.count++ }
func main() { m := map[string]Counter{"a": {}} m["a"].Inc() // map values are not addressable}Compiling this with Go 1.26.5 gives:
./main.go:15:9: cannot call pointer method Inc on CounterThe reason is structural: a map can rehash and move its entries at any time, so Go refuses to hand out a stable address into it. The fix is either to store pointers (map[string]*Counter) or to copy out, mutate, and store back.
The same error appears for a composite literal used directly, because a literal has no variable to take the address of:
Counter{}.Inc()./main.go:12:12: cannot call pointer method Inc on CounterAddressability also decides interface satisfaction. T’s method set only has value-receiver methods, so assigning a T value to an interface that needs a pointer-receiver method fails at compile time, not at the call:
type Incer interface{ Inc() }
var i Incer = Counter{}./main.go:16:16: cannot use Counter{} (value of struct type Counter) as Incer value in variable declaration: Counter does not implement Incer (method Inc has pointer receiver)var i Incer = &Counter{} compiles, because *Counter’s method set includes Inc.
Method values vs method expressions
Section titled “Method values vs method expressions”A method value — c.Inc — is a function bound to the specific receiver c at the moment you take it. A method expression — Counter.Value — takes the type itself, and the receiver becomes the function’s first parameter:
c := &Counter{}inc := c.Inc // bound to c already — inc() means c.Inc()inc()
value := Counter.Value // unbound — receiver is the first argumentfmt.Println(value(*c))Method values are handy for passing a bound method as a callback (http.HandleFunc("/x", h.serveX)); method expressions are rare, mostly seen in generic code that needs to call a method on a type parameter’s underlying type.
Nil pointer receivers
Section titled “Nil pointer receivers”Calling a method through a nil pointer is legal in Go, as long as the method body never dereferences a nil field before checking for it. This is the standard way to write a recursive nil-safe method on a linked structure:
type List struct { val int next *List}
func (l *List) Sum() int { if l == nil { return 0 } return l.val + l.next.Sum()}var l *List; l.Sum() returns 0 without panicking — the method call itself does not dereference l, and the body’s own nil check handles it.
Receiver naming
Section titled “Receiver naming”Idiomatic Go receivers are one or two letters derived from the type name — c Counter, r *Rectangle — never this or self. Keep the same receiver name for every method on a type; the standard library follows this throughout (func (b *Buffer) WriteString, func (t Time) Add).
Methods on a named function type
Section titled “Methods on a named function type”Because any named type can have methods, a named function type can too. The standard library uses this to turn a plain function into something that satisfies an interface — http.HandlerFunc is a func(http.ResponseWriter, *http.Request) with a ServeHTTP method, which is the only thing http.Handler requires:
type IntOp func(int) int
func (f IntOp) Twice(x int) int { return f(f(x))}IntOp(double).Twice(3) calls double on its own result — the function itself is the receiver.
Copying a mutex: the value-receiver trap
Section titled “Copying a mutex: the value-receiver trap”sync.Mutex must never be copied after first use, because a copy has its own independent lock state. A value receiver copies the whole struct on every call — so a value-receiver method on a struct containing a mutex silently locks a copy, protecting nothing:
type Safe struct { mu sync.Mutex count int}
func (s Safe) Inc() { // value receiver — copies mu on every call s.mu.Lock() defer s.mu.Unlock() s.count++}go vet catches this exact bug:
main.go:11:9: Inc passes lock by value: command-line-arguments.Safe contains sync.MutexThe fix is always a pointer receiver: func (s *Safe) Inc(). Any struct holding a sync.Mutex, sync.WaitGroup, or similar must use pointer receivers on every method, and should never be passed or returned by value.
package main
import "fmt"
type Counter struct { count int}
// Pointer receiver — mutates the structfunc (c *Counter) Inc() { c.count++}
// Value receiver — read-onlyfunc (c Counter) Value() int { return c.count}
type Celsius float64
func (t Celsius) String() string { return fmt.Sprintf("%.1f\u00b0C", float64(t))}
// List demonstrates a nil pointer receiver: Sum handles a nil *List gracefully.type List struct { val int next *List}
func (l *List) Sum() int { if l == nil { return 0 } return l.val + l.next.Sum()}
// IntOp is a named function type with its own method.type IntOp func(int) int
func (f IntOp) Twice(x int) int { return f(f(x))}
func main() { c := &Counter{} c.Inc() c.Inc() c.Inc() fmt.Println(c.Value()) // 3
temp := Celsius(36.6) fmt.Println(temp.String()) // 36.6°C
// Method value: bound to c the moment it is created. inc := c.Inc inc() fmt.Println(c.Value()) // 4
// Method expression: receiver becomes an explicit first argument. value := Counter.Value fmt.Println(value(*c)) // 4
var empty *List fmt.Println(empty.Sum()) // 0 — nil receiver, handled inside Sum
list := &List{val: 1, next: &List{val: 2}} fmt.Println(list.Sum()) // 3
double := IntOp(func(x int) int { return x * 2 }) fmt.Println(double.Twice(3)) // 12} Trade-offs
Section titled “Trade-offs”| What you get | Benefit | Cost |
|---|---|---|
| Value receiver | Cheap to reason about, callable on non-addressable values | Copies the struct on every call |
| Pointer receiver | Can mutate, no copy | Needs a nil-check, receiver must be addressable |
| Method on a non-struct type | Extends the behavior of any named type in your package | Cannot add a method to a type from another package |
| Method value | Bundles a receiver with a function, easy to pass as a callback | Keeps the receiver alive for the closure’s lifetime |
| Pointer receiver on a type holding a mutex | The only correct option | Every method — even read-only ones — must take a pointer |
Common Misconceptions
Section titled “Common Misconceptions”- Value and pointer receivers are interchangeable. A value receiver works on a copy — it cannot mutate the caller’s original value.
- You can add a method to a type from another package. Go does not allow this; wrap the type in a local named type or use embedding instead.
Tand*Thave the same method set.*T’s method set is larger — it also includes the pointer-receiver methods, which affects interface satisfaction.- A map value with a pointer-receiver method just works, like a struct variable. It does not — map values are not addressable, so
m[k].Inc()is a compile error, not a runtime surprise. - Copying a struct that embeds
sync.Mutexis harmless if you don’tLockright away. The copy is already broken the moment it exists;go vetflags the copy itself, not just the lock call.
💡 Real-world example
net/http:http.ResponseWriteris an interface — everyHandleraccepts it instead of a concrete type, which makes handlers easy to test with a mock.http.HandlerFuncis a named function type with aServeHTTPmethod, so a plain function can satisfyhttp.Handlerwith a one-line conversion.
*bytes.Bufferand*http.Clientboth use a pointer receiver on every method because they hold internal state that methods mutate (buffered bytes, a connection pool) — consistency of receiver kind matters here.
Reading AI-generated code
Section titled “Reading AI-generated code”When a generated snippet defines methods, check for these before trusting it:
- Does every method on the type use the same receiver kind — all value, or all pointer? A mix is a warning sign.
- If a method mutates a field, is the receiver a pointer? A value-receiver mutation compiles and silently does nothing.
- Does the type’s struct contain a
sync.Mutex,sync.WaitGroup, or similar? Every method must be a pointer receiver, and the type must never be copied or passed by value. - Is a pointer-receiver method ever called on a map value (
m[k].Method()) or a fresh composite literal (T{}.Method())? Both fail to compile — addressability, not a runtime bug. - Does an interface variable get assigned a value type (
var i Iface = T{}) when the required method has a pointer receiver? That’s a compile error the generated code may have never actually been run against.
🐛 Spot the bug
type Counter struct{ n int }
func (c *Counter) Inc() { c.n++ }
func main() { m := map[string]Counter{"a": {}} m["a"].Inc()}Show the answer
m["a"] is not addressable — a map value can be moved by a rehash at any time, so Go refuses to take its address for the pointer-receiver call. The real compiler message is cannot call pointer method Inc on Counter. Fix it by using map[string]*Counter, or by loading the value, mutating it, and storing it back: c := m["a"]; c.Inc(); m["a"] = c.