Right, let’s talk about something that seems like it should be simple but trips up a lot of smart people: getting a handle on a method itself, not just calling it on a specific variable. We’re talking about method values and method expressions. The difference is subtle in name but massive in practice. It all comes down to one question: is the receiver already baked in, or do you have to supply it later?

Think of it this way: a method value is a function that has already locked onto its receiver. It’s a loyal hound that knows exactly who it’s fetching for. A method expression is more like a function that’s still waiting for its receiver. It’s a stray; you have to give it a home before it’ll do anything useful.

Method Values: The Loyal Hound

You get a method value by referencing a method from a specific instance, using the good old dot notation. The key here is that the receiver—the specific instance you got it from—is already bound to it. Forever. You can pass this function around, call it later, and it will always operate on that original receiver.

type CoffeeMaker struct {
    temperature int
}

func (c *CoffeeMaker) Brew() {
    fmt.Printf("Brewing at exactly %d°F. Perfect.\n", c.temperature)
}

func main() {
    myMaker := &CoffeeMaker{temperature: 205}
    
    // This is a Method Value.
    // `brewFunction` is now a function that takes no arguments
    // and will always call `Brew` on `myMaker`.
    brewFunction := myMaker.Brew
    
    // We can call it directly. It knows what to do.
    brewFunction() // Prints "Brewing at exactly 205°F. Perfect."
    
    // Let's prove it's bound to `myMaker` and not some new instance.
    myMaker.temperature = 190
    brewFunction() // Prints "Brewing at exactly 190°F. ...less perfect."
}

See? brewFunction isn’t just the Brew method; it’s myMaker.Brew. The receiver is part of the package. This is incredibly useful for things like defer statements or hooking into libraries that expect a func().

Method Expressions: The Clever Stray

Now, a method expression references the method on the type itself, not an instance. It looks like Type.MethodName. This creates a function that requires its first argument to be the receiver. It’s waiting for you to tell it what instance to operate on.

The syntax here is the one that makes everyone’s eyes glaze over at first. For a value receiver method func (t T) Method(), the expression T.Method expects a T. For a pointer receiver method func (t *T) Method(), the expression (*T).Method expects a *T. Yes, the pointer receiver version needs the type in parentheses. The designers thought this was a good idea for clarity. I have… opinions.

func (c CoffeeMaker) Clean() {
    fmt.Println("Cleaning the value receiver way!")
}

func main() {
    myMaker := CoffeeMaker{}
    
    // This is a Method Expression.
    // `cleanFunc` is of type `func(CoffeeMaker)`
    cleanFunc := CoffeeMaker.Clean
    // We MUST pass a CoffeeMaker as the first argument.
    cleanFunc(myMaker) // Works.
    
    // For the pointer receiver method, note the (*Type) syntax.
    brewFuncExpr := (*CoffeeMaker).Brew
    // We MUST pass a *CoffeeMaker as the first argument.
    brewFuncExpr(&myMaker) // Works.
    
    // This is a classic "wtf" moment and will not compile:
    // brewFuncExpr(myMaker)
    // You're trying to pass a CoffeeMaker where a *CoffeeMaker is expected.
}

The power here is one of indirection. You can write a function that takes a method expression as a parameter, and then you can call any method matching that signature on any instance you provide later. It’s a way to parameterize not just data, but behavior.

The Interface Illusion (And Why It’s Brilliant)

Here’s the real magic trick Go pulls off. Both method values and method expressions have function types. And if that function type matches an interface, you can pass them wherever that interface is expected. This is how http.HandlerFunc works, and it’s a thing of beauty.

// A function type that matches the Handler interface.
type BrewFunc func()

// This method lets BrewFunc satisfy the Handler interface.
func (f BrewFunc) Brew() {
    f() // Just call the underlying function.
}

func main() {
    maker := &CoffeeMaker{temperature: 200}
    
    // Create a method value.
    value := maker.Brew
    
    // Assign it to our custom function type.
    var handler BrewFunc = value
    
    // Because BrewFunc has a Brew() method, we can pass it
    // to anything that expects a Handler.
    handler.Brew()
}

This pattern is everywhere in the standard library because it’s clean, efficient, and avoids the need for endless boilerplate structs just to implement a single interface method.

The Gotcha: Nil Receivers and Early Binding

Remember how the method value binds the receiver early? This has a crucial implication. If you create a method value from a nil pointer receiver, you’ve just baked that nil right into the function. You won’t discover the problem until you try to call it.

var nilMaker *CoffeeMaker = nil

// This compiles just fine! We're just capturing a method value.
brewFromNil := nilMaker.Brew

// But the moment you call it... BOOM. panic: runtime error: invalid memory address
// brewFromNil()

The method expression, by contrast, would force you to pass the nil value explicitly at the call site, which might make the problem more obvious. This early binding is a trade-off. The convenience of having the receiver ready to go is fantastic, but it means you have to be sure the receiver is in a valid state at the time you create the method value, not just when you call it. It’s one of those things you learn the hard way exactly once. Consider this your vaccination.