Why a Go Pointer-Receiver Method Can Be Called but Doesn't Satisfy the Interface: Method Sets and Addressability

c.Inc() compiles, yet assigning c to an interface fails. This post uses the Go spec's method set and addressability rules to explain why, with map elements, embedding and method values.

gomethod-setpointer-receiverinterface

In Go, calling a pointer-receiver method on a value variable compiles without complaint. Assign that same variable to an interface that requires the method, though, and you get a compile error. The method is right there, yet the type does not implement the interface.

The two behaviours come from different rules in the specification. Whether a type implements an interface is decided by its method set. Being able to call a pointer method on a value comes from a call rule that only applies to addressable values. This post takes the two rules apart and shows where the gap between them surfaces: map elements, function results, embedding and method values.

The baseline is the Go language specification (Language version go1.27, May 26, 2026), the Go FAQ and Effective Go, all checked on 2026-10-27. None of these rules is new; they follow from the basic definition of method sets. Methods on generic types are out of scope. The “compile error” notes and results in the examples follow from the spec’s rules; they were not produced by running the code for this article. The spec does not define the compiler’s error text, so none is quoted.

The example type

The whole post uses this type.

type Counter struct{ n int }

func (c *Counter) Inc()      { c.n++ }      // pointer receiver
func (c Counter) Value() int { return c.n } // value receiver

type Incrementer interface{ Inc() }
var c Counter
c.Inc()                // compiles
var _ Incrementer = c  // compile error
var _ Incrementer = &c // compiles

Method sets: T and *T differ

The “Method sets” section says every type has a method set, and that set determines which methods can be called on an operand of that type. For defined types there are two rules.

Type Method set
T methods with receiver T
*T (T is neither a pointer nor an interface) methods with receiver *T or T

So the method set of Counter holds only Value, while that of *Counter holds both Inc and Value. The set for *T contains the set for T, not the other way round.

Interface implementation is decided on these sets. According to the “Interface types” section, the type set of an interface that only lists methods is the set of types implementing all of them, and the type set of a single method specification is every non-interface type whose method set includes that method. A non-interface type T implements an interface when it is in the interface’s type set. Inc is not in the method set of Counter, so Counter does not implement Incrementer; *Counter does.

So why does c.Inc() work?

Calls have their own rule. The “Calls” section says x.m() is valid if the method set of x contains m, and then adds an exception: if x is addressable and &x’s method set contains m, x.m() is shorthand for (&x).m(). c is a variable, so it is addressable, and c.Inc() is handled as (&c).Inc().

This shorthand exists for method calls (and, as shown later, method values). Assignment to an interface only asks whether the assigned value’s type implements the interface. No rule takes the address for you there.

The opposite direction, calling a value method through a pointer, is covered by method sets alone, because Value is in the method set of *Counter. Under the “Selectors” rules, for a value x of type T or *T, x.f denotes the field or method in T, and the spec’s examples spell p.M1() (receiver T1) out as ((*p).T1).M1(), a call on the dereferenced value.

Why interfaces get no automatic &

The FAQ entry “Why do T and *T have different method sets?” gives the reason. If an interface value holds a pointer *T, a method call can get a value by dereferencing the pointer. If the interface value holds a T value, there is no safe way for a method call to get a pointer. Allowing it would let a method modify the value stored inside the interface, which the specification does not permit.

The FAQ adds that even where the compiler could take an address, changes made by the method would be lost to the caller. Its example:

var buf bytes.Buffer
io.Copy(buf, os.Stdin) // not allowed

If this were valid, standard input would be copied into a copy of buf, not into buf itself. The FAQ says that is almost never what you want, so the language disallows it. bytes.Buffer’s Write has a pointer receiver, so you pass the address: io.Copy(&buf, os.Stdin).

Effective Go puts the rule in one sentence: value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers. It then describes the convenience exception: when the value is addressable, the language inserts the address operator for you. Its ByteSlice example passes &b to fmt.Fprintf(&b, ...) for the same reason: only *ByteSlice satisfies io.Writer.

Non-addressable values can’t make the call either

The x.m() shorthand applies only when x is addressable, so it matters which values are. The “Address operators” section limits addressable operands to:

  • a variable
  • a pointer indirection (*p)
  • a slice indexing operation (s[i])
  • a field selector of an addressable struct operand
  • an array indexing operation of an addressable array

Map elements, function call results and composite literals are the usual values missing from that list.

func newCounter() Counter { return Counter{} } // package-level function
s := []Counter{{}}
s[0].Inc() // compiles: slice elements are addressable

m := map[string]Counter{"a": {}}
m["a"].Inc()   // compile error
m["a"].Value() // compiles: value receiver, no address needed

newCounter().Inc() // compile error

Counter{}.Inc()    // compile error
(&Counter{}).Inc() // compiles

The spec has no single sentence saying map elements cannot be addressed. But map indexing is absent from the list of addressable operands, and the assignment section lists left-hand operands separately as “addressable, a map index expression, or the blank identifier”. The conclusion that a map element can be assigned to but is not an addressable operand comes from reading those two rules together.

Function results appear directly in the spec’s method value examples: makeT().Mp is invalid because the result of makeT() is not addressable. The call newCounter().Inc() depends on the same condition and fails for the same reason.

Composite literals are a slightly different case. The “Address operators” section allows a composite literal as the operand of & as an exception to the addressability requirement, and “Composite literals” says that taking the address of one creates a pointer to a unique variable initialized with the literal’s value. That exception covers writing & yourself; it does not make the literal addressable. So Counter{}.Inc() does not get the shorthand, while (&Counter{}).Inc() works. That last point is an inference from the spec’s definitions, not a sentence it states.

There are two common ways to change a map value through a pointer method.

// 1. Copy out, modify, store back
c := m["a"]
c.Inc()
m["a"] = c

// 2. Store pointers from the start
pm := map[string]*Counter{"a": {}}
pm["a"].Inc() // pm["a"] is a *Counter value, so this is a plain call

In the second form, pm["a"] is still not addressable, but the value itself is a *Counter whose method set contains Inc, so no shorthand is needed.

What embedding does to method sets

The “Struct types” section has separate rules for methods promoted from embedded fields. For a struct type S and a type name T:

  • If S embeds T, the method sets of S and *S both include promoted methods with receiver T. The method set of *S also includes promoted methods with receiver *T.
  • If S embeds *T, the method sets of S and *S both include promoted methods with receiver T or *T.
type Named struct{ Counter }     // embedded by value
type NamedPtr struct{ *Counter } // embedded by pointer

var n Named
var _ Incrementer = n  // compile error: Inc not in Named's method set
var _ Incrementer = &n // compiles

np := NamedPtr{&Counter{}}
var _ Incrementer = np // compiles

Embedding by value passes the T/*T split on to the outer type. Embedding by pointer puts the pointer methods into the outer struct value’s method set. On the other hand, NamedPtr{} gives you a struct whose embedded pointer is nil, so the choice comes down partly to whether your code is ready to handle a nil field.

Method values and method expressions

The same rules apply when you take a method as a function value instead of calling it. The “Method values” section says that, as with calls, referring to a pointer-receiver method through an addressable value takes the address automatically (t.Mp is (&t).Mp), and referring to a value-receiver method through a pointer dereferences automatically (pt.Mv is (*pt).Mv).

The same section says the receiver expression is evaluated and saved when the method value is created, and the saved copy is used as the receiver in later calls. Applied to Counter, that gives:

var c Counter
inc := c.Inc     // (&c).Inc: the pointer &c is saved
val := c.Value   // a copy of c at this moment (n == 0) is saved
inc()
inc()
fmt.Println(c.Value()) // 2
fmt.Println(val())     // 0

inc holds the address of c and changes it; val keeps using the copy taken when it was created. The spec’s own example shows the same thing: after the method values are created, *t = 42 does not affect the stored receivers.

Method expressions convert in one direction only. According to “Method expressions”, you can derive a function with an explicit pointer receiver from a value-receiver method, (*T).Mv, but T.Mp, a value-receiver function for a pointer-receiver method, is illegal because pointer-receiver methods are not in the value type’s method set. (*Counter).Value is fine; Counter.Inc is not.

A pointer to an interface has no methods

The second method set rule only applies when T is neither a pointer nor an interface. The spec says any other type has an empty method set, so a pointer to an interface such as *io.Writer has an empty method set. The FAQ entry “When should I use a pointer to an interface?” reaches the same conclusion: passing var w io.Writer as fmt.Fprintf(&w, ...) does not compile, and although a pointer to a concrete type can satisfy an interface, a pointer to an interface cannot (apart from assignment to the empty interface). Fixing “the interface needs a pointer” by adding & is only right when the value is of a concrete type.

Choosing a receiver

The FAQ entry “Should I define methods on values or pointers?” points out that a receiver behaves exactly like an argument to the method, so choosing between value and pointer is the same question as for a function argument. Its considerations:

  • If the method needs to modify the receiver, it must be a pointer. Slices and maps act as references, which makes this a little more subtle, but changing a slice’s length inside a method still needs a pointer receiver.
  • For a large receiver, such as a big struct, a pointer receiver may be cheaper.
  • Consistency: if some methods must have pointer receivers, the rest should too, so the method set is consistent regardless of how the type is used.
  • For values that are cheap to copy, such as basic types, slices and small structs, a value receiver is efficient and clear unless the method’s semantics require a pointer.

The consistency point connects directly to the problem in this post. Mixing receivers as Counter does gives Counter and *Counter different method sets, and some interfaces end up implemented only by *Counter.

You can check at compile time that a type implements the intended interface, using the pattern the FAQ shows.

var _ Incrementer = (*Counter)(nil) // check that *Counter implements Incrementer

If the Counter value itself should implement it, write var _ Incrementer = Counter{}. If the line fails to compile, the implementation is incomplete.

Summary

  • The method set of T holds methods with receiver T; the method set of *T holds methods with receiver T or *T. Interface implementation is decided on these sets.
  • Calling a pointer method on a value variable works through the call shorthand (&x).m() for addressable x. Assignment to an interface has no such shorthand.
  • Map elements, function results and composite literals themselves are not addressable, so pointer methods cannot be called on them. For maps, store pointers, or copy out, modify and store back.
  • Embedding T passes the split on; embedding *T gives the outer value the pointer methods too.
  • A pointer to an interface has an empty method set.

For a different problem with interfaces, a nil pointer stored in one, see why a Go error holding a nil pointer is not nil.

Sources

All checked on 2026-10-27.

  • The Go Programming Language Specification (Language version go1.27, May 26, 2026): https://go.dev/ref/spec (“Method sets”, “Interface types”, “Struct types”, “Selectors”, “Calls”, “Address operators”, “Composite literals”, “Assignment statements”, “Method values”, “Method expressions”)
  • Go FAQ: https://go.dev/doc/faq (“Why do T and *T have different method sets?”, “Should I define methods on values or pointers?”, “When should I use a pointer to an interface?”, “How can I guarantee my type satisfies an interface?”)
  • Effective Go, “Pointers vs. Values”: https://go.dev/doc/effective_go (the page notes it was written for Go’s 2009 release and is not actively updated)

Back to the blog