Mixing method receivers on a type

Hi,

Is it acceptable/valid practise to create mixed receivers on a type? As shown below bind is pointer receiver because it is mutating the type but validate is a copy receiver as it is not mutating the type. Although it would work, is it idiomatic Go?

Thanks

type Request struct {
  Name  string
  Price int
}

func (r *Request) bind(name string) error {
  r.Name = name
}

func (r Request) validate() error {
  if r.Name == "yow" {
    return some error
  }
  return nil
}

Hi @yeheke,

This works, and I don’t think this is against any idioms.

However, there is a catch. If you add an interface like this one:

type Requester interface {
    bind(string) error
    validate() error
}

and a function

func iWantARequester(r Requester) {}

then you cannot pass a variable of type Request to the function. You would get an error saying,

“Request does not implement Requester (bind method has pointer receiver)”

Why? Because method bind() cannot be called on a type Request. You need a pointer type here, &Request, so that bind() can access the receiver through the pointer.

The non-pointer type Request “owns” only one of the two methods, validate().

A pointer type &Request, on the other hand, owns both methods because it is able to follow the pointer receiver in bind() as well as access the non-pointer receiver in validate().

I am not sure if the explanation is clear enough. Here is a runnable code in the playground to show the difference. Run this code and get an error at iWantARequester(r). Comment out the code around r and you’ll see that pr works fine.

And here is a link to the specification of method sets that explains the situation this way:

  • The method set of a defined type T consists of all methods declared with receiver type T.
  • The method set of a pointer to a defined type T (where T is neither a pointer nor an interface) is the set of all methods declared with receiver *T or T.

(emphasis mine)

T would be Request in your code, and “a pointer to a defined type T” would be &Request.

2 Likes

It makes sense. I wasn’t aware of the interface catch though. Thank you for the detailed explanation.

1 Like

You’re welcome.

In addition to the above reasoning, here is an article that strongly advises defaulting to pointer receivers for any method:

Should methods be declared on T or *T | Dave Cheney

The point that Dave Cheney makes is that a method with a non-pointer receiver might inadvertently copy a value that is not supposed to be copied, such as a struct containing a mutex.

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