Invalid receiver type when underlying type is a pointer to a type

In the following example I can define a receiver for the pointer to the type (*dog). However if I create a new type that has the underlying type *dog (type Dog *dog) I get the error invalid receiver type. Trying to understand why is it so

When you say, “why,” I’m not sure if you’re asking “for what reason,” or “by what cause?” The “Method declarations” section of the language specification (link) says:

A receiver base type cannot be a pointer or interface type and it must be defined in the same package as the method.

I do not know why the language designers made the decision to disallow this.

1 Like

Hi

Thanks for the response. Was kind of curious as to why the language designers did this…

As a disclaimer, my answer here would be better suited to #technical-discussion because it’s anecdotal, but because the question is in #getting-help and I haven’t seen any scientific answers, I’ll post my guess:

I’m guessing it has to do with the “magic” that allows you to call T and *T methods on either a T or a *T value, depending on the situation. For example, you can call T.Method when you have a *T, and you can call a (*T).Method on a T if that T is addressable. If the language allowed you to define methods on double-pointers, I’m not sure how that magic would work:

type T struct {
	I int
}

func (t T) DoSomething() { }

// Should you be allowed to do this?  It wouldn't overlap with
// T or *T's method sets, so maybe it's OK?
func (t **T) DoSomething() { }

// Does this DoSomething belong to the {T, *T} method set or
// the {*T, **T} method set?
// func(t *T) DoSomething() { }

If Go didn’t have this magic that lets you implicitly mix T/*T methods and values (depending on the circumstances), it would probably make the problem of the method sets go away: T.DoSomething and (*T).DoSomething would be different methods just like T.DoSomething vs. U.DoSomething, but then if you wanted to call a pointer method on a value, you’d have to take the address first otherwise, you’d have ambiguous calls:

type T struct {
	I int
}

func (t T) DoSomething() { }

func (t **T) DoSomething() { }

func main() {
	t := &T{}
	// Did you mean to call (*t).DoSomething()
	// or (&t).DoSomething()?
	t.DoSomething()
}

You could probably add features to the compiler so that it would still allow the method sets of T/*Ts to overlap and then just deem ambiguous usages as errors, but that becomes harder to explain. That might not be a huge problem in pre-Go 1.18 because it’s probably infrequently that you want to use a **T, but that problem might become more significant with the addition of generics where the generic type (or function) expects a *T and the T you pass in is itself a pointer type like *U, but you expected it to be, e.g., a U. If you have the necessary methods defined on both U and *U, then your code will compile, but do something you didn’t want.

I’m sure there are ways to work through all these scenarios, but you have to remember that the language designers don’t want to add features to the language. They want to keep it small. It’s easy to learn Go relative to C++, C#, or Python because the actual syntax of the language is much smaller. The real learning is in the how people use the language like “conventions,” or “patterns,” etc.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.