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.