SECTION 2.5

Methods & Receivers

pod.SetStatus("Running") versus MarkRunning(pod) is the choice between method and free function. The receiver kind — value or pointer — decides whether the call mutates the pod or silently writes to a copy.

Methods are functions attached to a type. In Go, you define them outside the struct — they're just functions with a receiver parameter.

Value Receivers

type Counter struct{ n int }

func (c Counter) Inc() { c.n++ }

func main() { c := Counter{} c.Inc() c.Inc() c.Inc() fmt.Println(c.n) }

Inc has a value receiver — every call gets its own copy of c, increments that copy's n, and throws the copy away when the method returns. The original is never touched. This is the most common method-receiver bug; the fix is a pointer receiver (next section).

// Value receiver — gets a COPY of the struct
func (p Pod) FullName() string {
    return p.Namespace + "/" + p.Name
}

func (p Pod) IsRunning() bool {
    return p.Status == "Running"
}

// Usage
pod := Pod{Name: "web-1", Namespace: "production", Status: "Running"}
fmt.Println(pod.FullName())  // "production/web-1"
fmt.Println(pod.IsRunning()) // true

Pointer Receivers

// Pointer receiver — can MODIFY the original struct
func (p *Pod) SetStatus(status string) {
    p.Status = status
}

func (p *Pod) AddLabel(key, value string) {
    if p.Labels == nil {
        p.Labels = make(map[string]string)
    }
    p.Labels[key] = value
}

// Usage
pod := Pod{Name: "web-1", Status: "Pending"}
pod.SetStatus("Running")  // modifies pod directly
fmt.Println(pod.Status)    // "Running"

When to Use Which

Use value receiver Use pointer receiver
Method only reads fields Method modifies fields
Struct is small (< 5 fields) Struct is large
You want immutable semantics You need mutation

Practical rule: If any method on a type needs a pointer receiver, use pointer receivers for all methods on that type. Mixing receivers is confusing and causes subtle interface satisfaction bugs.

Python comparison
# Python: all methods get `self` (always a reference, always mutable)
class Pod:
    def full_name(self):
        return f"{self.namespace}/{self.name}"

# Go: you explicitly choose value vs pointer receiver.
# More typing, but you know exactly what can mutate.