The basic idea about interfaces and methods is (this is the pre-generic
view point, which is relevant here):

1) An interface defines methods (an interface *is* a method set)
2) Methods can be attached to (associated with) *any* type (excluding
interfaces which have their own syntax for methods)
3) A value of a type that has all the methods of an interface type can be
assigned to a variable of that interface type
4) When a method is called on an interface, the dynamic type of the
interface determines which method to call.

Then, there were some explicit syntactic/semantic design decisions:

5) A function becomes a method by specifying its receiver parameter before
the method name. The receiver parameter type is the type to which the
method is attached.
6) A method can only be attached to a defined (named) type (and that type
cannot be an interface).

In this simple-most form, a value x of defined type T can be assigned to a
variable v of interface type I if T has all the methods of I. When a method
m of I is called,
the type T of the value x stored in v is used to determine the method m of
T to call with x as receiver value: so, if x is in v, v.m(...) dynamically
looks up T.m (T is the type of x) and then calls T.m(x, ...). Importantly,
the receiver value stored in the interface is simply passed through the
method m.
(Recall that an interface is a pair (type, value) where type is the type of
the value. Lookup uses the type and method name to find the right method.)

In this scenario, the method set of a type T is simply the methods with
receiver type T ("the T methods").

In reality, this is cumbersome because it means that if we want to pass a
reference (a pointer) to a data structure, we would have to give that
pointer type an explicit name
so that we can attach methods to it.

More generally, even without methods, typically names are given to
non-pointer data types T, and then we simply use *T to denote the
respective pointer type.
That is, in practice we think of a *T not so much as a distinct type, but
simply as a reference to T, and we use *T instead of a specific type name
for it.
(This is a long-established coding practice, e.g. in C.)

But we still want to be able to pass receiver values as references
(pointers), for efficiency and sharing purposes.
Thus, we allow a receiver type to be of the form *T (an unnamed type) in
addition to T, and we say that methods are always associated with the
receiver base type (T in this case);
it's not possible to have two methods with the same name with a pointer and
non-pointer receiver.
And we also disallow named receiver types T that are pointers (methods
cannot be attached to pointers).

Now, given a defined type T, we can have methods

func (x T) m1 ...
func (x *T) m2 ...

with pointer and non-pointer receivers *T and T.

As before, if for each method m of an interface I there is a method m of T
with non-pointer receiver T, a value of x can be assigned to an I variable.
Similarly, if for each method m of an interface I there is a method m of T
with pointer receiver *T, a pointer to an x (&x) can be assigned to an I
variable.

That is, the receiver data again is simply passed through.
Until here there are no changes to the basic mechanism outlined above,
except that we allow T and *T types as receivers, where T must be a defined
non-pointer and non-interface type.

But given a pointer p (= &x) of type *T, one can always get to the x
through an indirection (assuming p is not nil). Therefore, we can include
all the methods of T with receiver type T
as part of the method set of *T. Which is why method sets for *T types
include the methods with T and *T receiver (and which is why the method
names
with T and *T receivers must be distinct).

Given an non-pointer method m

func (x T) m ...

the compiler automatically generates a wrapper method m'

func (p *T) m'(...) { (*p).m(...) }

as needed, which calls the non-pointer method. In this case, if m is called
on a nil-pointer p, there will be a run-time panic.
This is true whether m is called directly on p or through an interface
(after p was assigned to that interface).

Alternatively, one could call such methods via explicit indirection, as in
(*p).m(), but this doesn't work if m is called through an interface.
By including the T methods in the method set of *T, the automatic
indirection works also with interfaces; so there's a significant
convenience gained here.
On a more abstract level, including both *T and T methods in the method set
of *T reflects the view that *T values are just references of T data
structures.

Note that it's also possible to call an *T method on a variable x (but not
a value) of (non-pointer) T, even though the method set of
T doesn't include the methods with receiver type *T. More precisely, this
works if x is addressable (variables are addressable), in which case
the compiler automatically takes the address of x before calling the
method. This doesn't work through interfaces (once the x is in the
interface,
it's a copy of the original one, and taking that copy's address would lead
to a pointer to the wrong object). Which is why in this case the type set
is not augmented.

With both these two "adjustments" to the basic idea outlined at the start,
we can write methods with T and *T receivers and use them on T and *T
(references to T) and it usually "just works" (we cannot call a *T method
on a T value that is not addressable).
That is, we can concentrate on the data structure T and methods associated
with T; whether we use a *T or a T for receivers can be an orthogonal
decision based on whether sharing or efficiency is relevant.

(All this said, I don't know how inconvenient it would be if *T and T
method sets were strictly aligned with *T and T types. It would certainly be
simpler.)

I hope this helps.
I am not sure I remember any more design rationale here. Russ Cox might
have more to say.

- gri







On Wed, Jun 11, 2025 at 2:17 AM Alexander Shopov <a...@kambanaria.org> wrote:

> @Axel Wagner
> > What about my explanation was dissatisfying?
> Please do not take my comment to sound combative.
> You gave me *an* explanation but I was looking for *the* explanation.
> I was looking more for a design document, discussion, some piece of
> tribal knowledge.
> Of course there would be downsides for a different design but I am
> looking at the way pro-s and con-s were evaluated.
>
> @Roger Peppe
> Thanx. Quite informative - this gives me a pointer on what happens
> next and the fact that this behavior is bothersome for other people.
>
> Another data point: the catchPanic workaround in Go Standard lib came
> only in
> https://github.com/golang/go/commit/97a929aac95301b850fb855e8e2fa8cfbe47ef59
> This is post go 1.3 (June 2014) but pre 1.4 (Dec 2014). This means
> that 4 public releases of Go passed (1.0, 1.1, 1.2, .1.3) before this
> was implemented.
> Note the review of the patch https://codereview.appspot.com/4640043
> and also the comments. It does not sound like a widely known
> workaround.
>
> If anyone is interested further in this:
> git log -G'ValueOf.*Kind.*Ptr.*IsNil' gets some more details:
>
> 1. It is Rob Pike implementing this
> 2. Another place where you do not want such failures is in the AST
> tools:
> https://github.com/golang/tools/blob/master/go/ast/astutil/rewrite.go#L190
> 3. Another place json encoding/decoding:
> https://github.com/golang/go/blob/master/src/encoding/json/decode.go
>
> Compare with the Golang specification:
> 1. Spec started on Mar 2, 2008 -
>
> https://github.com/golang/go/commit/18c5b488a3b2e218c0e0cf2a7d4820d9da93a554
> by Robert Griesemer
> 2. First usage of the term is on May 8, 2009 -
>
> https://github.com/golang/go/commit/df46b3342ce54129af59e30ff6d9708347f61c75
> by Rob Pike
> 3.
> https://github.com/golang/go/commit/533dfd62919ecb69a39973cea32d06a1cb166687
> on May 13, and
> https://github.com/golang/go/commit/56809d0ade51d3bbd653ba9e9b7c54e2f4ec5f66
> on May 20 by Robert Griesemer stronly corroborate Rob Pike as
> originator of the terminilogy.
> For comparison Go 1 was released March 2012.
>
> I am really interested in the background of thinking about method
> sets, whether this behavior was thought of initially or it was just an
> emerging surprise. Rob has never called this a thing Go got wrong
> AFAIK.
> Was it programmers misusing the API that were possible to misuse that
> caused this implementation?
>
> Kind regards:
> al_shopov
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion visit 
https://groups.google.com/d/msgid/golang-nuts/CAKy0tf5sMM1Wrwyzwr1ZWuNgDHgwgUbEQW_KusFjcsf%3DwUJUYA%40mail.gmail.com.

Reply via email to