Methods are functions that are attached to types. They allow you to define behavior for your custom types, making your code more organized and object-oriented while maintaining Go’s simplicity.In Go, any type can have methods - not just structs. The type that receives the method is called the receiver.
Instead of passing a value as a parameter, you can attach a method directly to a type:
func printBook(b book) { fmt.Printf("%-15s: $%.2f\n", b.title, b.price)}func printGame(g game) { fmt.Printf("%-15s: $%.2f\n", g.title, g.price)}// Problem: Can't use the same function name// Problem: Caller must know which function to use
Value receivers receive a copy of the original value. Changes made inside the method don’t affect the original.
Value Receiver
type book struct { title string price float64}func (b book) print() { // b is a copy of the original book value fmt.Printf("%-15s: $%.2f\n", b.title, b.price)}func (b book) discount(ratio float64) { b.price *= (1 - ratio) // This only modifies the copy! // Original book is unchanged}func main() { mobydick := book{title: "moby dick", price: 10} mobydick.discount(0.5) mobydick.print() // Still $10.00 - unchanged!}
Pointer receivers receive a pointer to the original value. Changes made inside the method affect the original.
Pointer Receiver
type game struct { title string price float64}func (g *game) print() { // g is a pointer to the original game fmt.Printf("%-15s: $%.2f\n", g.title, g.price)}func (g *game) discount(ratio float64) { // g is a pointer - this modifies the original g.price *= (1 - ratio)}func main() { minecraft := game{title: "minecraft", price: 20} // Go automatically converts: minecraft.discount(0.1) // to: (&minecraft).discount(0.1) minecraft.discount(0.1) minecraft.print() // $18.00 - modified!}
Go automatically handles the conversion between values and pointers when calling methods. You can call minecraft.discount(0.1) even though discount expects *game.
type money float64func (m money) string() string { return fmt.Sprintf("$%.2f", m)}type point struct { x, y int}func (p point) distance() float64 { return math.Sqrt(float64(p.x*p.x + p.y*p.y))}
Use pointer receivers when:
The method needs to modify the receiver
The type is large (would be expensive to copy)
You want consistency (if one method uses pointer, use it for all)
The type contains sync primitives (must not be copied)
Large or Mutable Types
type huge struct { games [10_000_000]game // ~200MB}// Only copies a single pointerfunc (h *huge) addr() { fmt.Printf("%p\n", h)}// Calling this 10 times = ~2GB if not using pointer!// func (h huge) addr() {// fmt.Printf("%p\n", &h)// }
Important: If any method on a type has a pointer receiver, use pointer receivers for ALL methods on that type, even if some methods don’t modify the receiver.
Consistent Receivers
type game struct { title string price float64}// Both methods use pointer receivers for consistencyfunc (g *game) print() { fmt.Printf("%-15s: $%.2f\n", g.title, g.price)}func (g *game) discount(ratio float64) { g.price *= (1 - ratio)}
This consistency helps prevent subtle bugs and makes the API more predictable.
Methods aren’t limited to structs - you can attach them to any named type:
Methods on Named Types
type money float64func (m money) string() string { return fmt.Sprintf("$%.2f", m)}type list []stringfunc (l list) print() { for i, item := range l { fmt.Printf("%d: %s\n", i, item) }}type handler func(string) stringfunc (h handler) apply(s string) string { return h(s)}func main() { var m money = 19.99 fmt.Println(m.string()) // $19.99 l := list{"apple", "banana", "cherry"} l.print()}
You can only define methods on types declared in the same package. You can’t add methods to built-in types like int or string directly - you must create a named type first.
The receiver type affects which methods are in a type’s method set:
type book struct { title string}func (b book) print() { fmt.Println(b.title)}func main() { b := book{title: "Go Programming"} b.print() // ✓ Works pb := &book{title: "Go Programming"} pb.print() // ✓ Works - Go auto-dereferences}
type printer interface { print()}type book struct { title string}// Value receiverfunc (b book) print() { fmt.Println(b.title)}type game struct { title string}// Pointer receiverfunc (g *game) print() { fmt.Println(g.title)}func display(p printer) { p.print()}func main() { b := book{title: "Go"} display(b) // ✓ Works - book implements printer display(&b) // ✓ Works - *book also implements printer g := game{title: "Chess"} // display(g) // ✗ Error - game doesn't implement printer display(&g) // ✓ Works - *game implements printer}
If a method has a pointer receiver, only pointers to that type satisfy the interface. If a method has a value receiver, both values and pointers satisfy the interface.
Go provides convenient syntactic sugar for method calls:
Automatic Conversions
type counter struct { value int}func (c counter) get() int { return c.value}func (c *counter) increment() { c.value++}func main() { c := counter{value: 0} // Value receiver - both work c.get() // Direct call (&c).get() // Also works // Pointer receiver - both work (&c).increment() // Direct call c.increment() // Go converts to (&c).increment()}
Go automatically:
Dereferences pointers when calling value receiver methods
Takes addresses when calling pointer receiver methods
Each value receiver method call creates a copy of the receiver:
Copy Cost
type large struct { data [1000]int}// This copies 8KB on every call!func (l large) process() { // ...}// This copies only a pointer (8 bytes)func (l *large) process() { // ...}
Escape Analysis
The compiler determines whether values can stay on the stack or must move to the heap:
Heap Allocation
func (g *game) reference() *game { return g // Pointer escapes, may cause heap allocation}func (g game) copy() game { return g // Value doesn't escape, stays on stack}
type game struct { title string price float64}func (g game) print() { // Value receiver fmt.Println(g.title)}func (g *game) discount() { // Pointer receiver g.price *= 0.9}// This inconsistency can cause confusion and bugs
Good: Consistent
Good: All Pointers
type game struct { title string price float64}func (g *game) print() { // Pointer receiver fmt.Println(g.title)}func (g *game) discount() { // Pointer receiver g.price *= 0.9}