Methods
Methods are functions with a receiver. Go's method system is orthogonal β you can add methods to any named type in your package. Understanding the difference between value and pointer receivers is fundamental to writing correct Go code.
π― Learning Objectives
- Define methods with both value and pointer receivers
- Explain the critical difference: which receiver type modifies the original
- Understand method sets and why they matter for interfaces
- Add methods to non-struct types (int, string, etc.)
- Use method values and method expressions
- Implement the fmt.Stringer interface
- Understand how methods are dispatched at runtime
π Method Syntax
A method is a function with a special first parameter: the receiver. The receiver appears between func and the method name.
package main
import (
"fmt"
"math"
)
// Type definition
type Circle struct {
X, Y float64 // centre
Radius float64
}
// Value receiver β receives a COPY of the circle
// Use for: read-only operations, small structs, basic getters
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// Pointer receiver β receives a POINTER to the circle
// Use for: mutations, large structs, implementing interfaces that need mutation
func (c *Circle) Scale(factor float64) {
c.Radius *= factor // modifies the original circle
}
func (c *Circle) MoveTo(x, y float64) {
c.X = x
c.Y = y
}
// String() method β implements fmt.Stringer interface
// fmt.Println will use this automatically
func (c Circle) String() string {
return fmt.Sprintf("Circle{centre:(%.2f,%.2f) r=%.2f}", c.X, c.Y, c.Radius)
}
// Methods on non-struct types β any named type in the same package
type Celsius float64
type Fahrenheit float64
func (c Celsius) ToFahrenheit() Fahrenheit {
return Fahrenheit(c*9/5 + 32)
}
func (f Fahrenheit) ToCelsius() Celsius {
return Celsius((f - 32) * 5 / 9)
}
func (c Celsius) String() string {
return fmt.Sprintf("%.1fΒ°C", float64(c))
}
func main() {
c := Circle{X: 0, Y: 0, Radius: 5}
fmt.Println(c) // Circle{centre:(0.00,0.00) r=5.00}
fmt.Printf("Area: %.2f\n", c.Area()) // Area: 78.54
fmt.Printf("Perim: %.2f\n", c.Perimeter())
c.Scale(2) // pointer receiver β modifies c
fmt.Println("after Scale(2):", c) // r=10.00
c.MoveTo(3, 4)
fmt.Println("after MoveTo:", c) // centre:(3.00,4.00)
// Value receiver called on pointer β Go auto-dereferences
p := &Circle{Radius: 7}
fmt.Printf("p.Area() = %.2f\n", p.Area()) // works! auto-dereferences
// Pointer receiver called on addressable value β Go auto-addresses
// c.Scale(1.5) β c is addressable, so Go passes &c automatically
temp := Celsius(100)
fmt.Println(temp) // 100.0Β°C (uses String())
fmt.Println(temp.ToFahrenheit()) // 212
}βοΈ Value vs Pointer Receivers β The Decision
| Question | Value Receiver | Pointer Receiver |
|---|---|---|
| Does the method modify the receiver? | No | Yes β must use pointer |
| Is the struct large? | No (use value, cheaper to read) | Yes (pointer avoids copying) |
| Does the type have sync.Mutex or similar? | Never β mutex must not be copied | Yes β must use pointer |
| Does other code call it on non-addressable values? | Fine | Cannot (see method sets below) |
| Is consistency with other methods important? | Use one or the other consistently | Mix is confusing β pick one style |
If any method on a type needs a pointer receiver, then all methods should use pointer receivers. Mixing value and pointer receivers on the same type is confusing and creates inconsistent interface satisfaction.
package main
import "fmt"
type Counter struct {
count int
}
// β WRONG: value receiver β modifies a copy, not the original!
func (c Counter) BadIncrement() {
c.count++ // modifies the LOCAL COPY, not the original Counter
}
// β
CORRECT: pointer receiver β modifies the original
func (c *Counter) Increment() {
c.count++
}
func (c *Counter) Add(n int) {
c.count += n
}
func (c Counter) Value() int { // value receiver OK for read-only
return c.count
}
func (c *Counter) Reset() {
c.count = 0
}
func main() {
c := Counter{count: 0}
c.BadIncrement() // does nothing!
fmt.Println("after BadIncrement:", c.Value()) // 0
c.Increment()
c.Increment()
c.Add(10)
fmt.Println("after Increment x2 + Add(10):", c.Value()) // 12
c.Reset()
fmt.Println("after Reset:", c.Value()) // 0
}π Method Sets
The method set of a type determines which interfaces it satisfies. This is a subtle but important concept.
| Type | Method Set includes |
|---|---|
T (value) | All methods with value receiver (func (t T)) |
*T (pointer) | All methods with value receiver AND pointer receiver (func (t *T)) |
package main
import "fmt"
type Writer interface {
Write(data string) error
}
type Reader interface {
Read() (string, error)
}
type ReadWriter interface {
Reader
Writer
}
type Buffer struct {
data string
}
// Pointer receiver β only *Buffer implements Writer
func (b *Buffer) Write(data string) error {
b.data += data
return nil
}
// Value receiver β both Buffer and *Buffer implement Reader
func (b Buffer) Read() (string, error) {
return b.data, nil
}
func useWriter(w Writer) {
w.Write("hello")
}
func main() {
b := Buffer{}
pb := &Buffer{}
// *Buffer has method set: {Write, Read} β satisfies ReadWriter
var rw ReadWriter = pb // β works
rw.Write("world")
// Buffer (value) has method set: {Read} only β CANNOT satisfy Writer
// var w Writer = b // β compile error: Buffer does not implement Writer
_ = rw
// But Go auto-takes address when calling pointer methods on addressable values:
b.Write("test") // Go automatically passes &b β b must be addressable!
fmt.Println(b.Read()) // test
useWriter(pb) // fine β *Buffer implements Writer
// useWriter(b) // β compile error: Buffer does not implement Writer
}A value stored in an interface cannot have its address taken β it's not addressable. So if you store a Buffer value in an interface variable, you cannot call pointer receiver methods through the interface. This is why you should use pointer receiver methods consistently β *Buffer always works.
π§ Method Values & Method Expressions
package main
import "fmt"
type Adder struct{ base int }
func (a Adder) Add(x int) int { return a.base + x }
func main() {
a := Adder{base: 10}
// Method value β binds the receiver
addFn := a.Add // addFn is func(int) int, a is already bound
fmt.Println(addFn(5)) // 15
fmt.Println(addFn(20)) // 30
// Useful: pass as a callback
numbers := []int{1, 2, 3, 4, 5}
results := mapInts(numbers, addFn)
fmt.Println(results) // [11 12 13 14 15]
// Method expression β receiver passed explicitly
addExpr := Adder.Add // addExpr is func(Adder, int) int
fmt.Println(addExpr(a, 5)) // 15
fmt.Println(addExpr(Adder{base: 100}, 5)) // 105
}
func mapInts(s []int, fn func(int) int) []int {
r := make([]int, len(s))
for i, v := range s { r[i] = fn(v) }
return r
}π€ Interview Questions
Value receiver (t T): the method receives a copy of the value. Modifications inside the method do not affect the original. Safe to call on both values and pointers (Go auto-dereferences).
Pointer receiver (t *T): the method receives a pointer to the original. Modifications affect the original. Can only be called via the interface when the value is addressable.
Use pointer receivers when: (1) the method must modify the receiver; (2) the struct is large (avoid copy cost); (3) the struct contains a mutex (must not be copied). In practice, most types in Go use pointer receivers for consistency.
Automatic conversion: Go automatically takes the address of an addressable value to call a pointer method (v.Method() β (&v).Method()). But this doesn't apply when the value is stored in an interface (not addressable).
Method set of T (value): includes only methods with value receivers.
Method set of *T (pointer): includes methods with BOTH value and pointer receivers.
This matters for interfaces because interface satisfaction is determined by method sets. If an interface requires a method with a pointer receiver, only *T (not T) satisfies it. This is why: (1) you should almost always pass *T to functions expecting an interface; (2) storing T in an interface and calling pointer methods through it causes a compile error.
Common mistake: implementing all methods with pointer receivers but trying to store a value (not pointer) in an interface variable. Fix: always use *T with interfaces when you have pointer receiver methods.
π§ͺ Quiz
c.count++, the original count is:T or *T?type MyInt int?fmt.Println use your custom output format?π Cheat Sheet
func (t T) Method() { }func (t *T) Mutate() { t.x++ }func (t T) String() string { }Only value receiver methodsValue + pointer receiver methodsv.PtrMethod() β (&v).PtrMethod()type C float64; func (c C) F() { }fn := obj.Method // bound receiverfn := T.Method // receiver as argAlways use pointer receiver!