Limitations on methods, part 3

August 30, 2023

This is part 3 of 3 of a mini-series on method limitations. If you missed the earlier installations, here are links to part 1 and part 2.

Method declarations

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

And now for the conclusion:

A receiver base type must be defined in the same package as the method.

This rule often confuses people coming from object-oriented languages, who want to extend an existing type with their own custom methods. Let’s first consider an example that violates the rule:

package person

type Person struct {
	Name string
}

Then in another package:

package employee

import "example.com/person"

func (p person.Person) Hire() error { // syntax error: non-declaration statement outside function body
	/* ... */
}

So how do you extend a type defined in another package?

Well, in short: You don’t.

At least not in the same way you may be accustomed to in classically object-oriented languages. There is no subclassing in Go. There is, however, embedding. And we can think of embedding as a way to extend a type. But it requires creating a new type. Let’s make the above example work, by modifying only our employee package:

package employee

import "example.com/person"

type Employee struct {
	person.Person // Embed the person.Person type
}

func (e Employee) Hire() error { // Now this is valid
	/* ... */
}

Now one thing to be aware of here: The embedded person.Person struct has no concept, idea, or inclination that it’s part of some other type. There is no way within a method defined on person.Person to detect that the instance is a member of an employee.Employee struct, as there often is in some OO languages.

Getting around this “limitation” requires an architectural redesign, which I would be happy to discuss if you find this is tripping you up. It would be best discussed over real code, so perhaps a good discussion for a future live stream. If this is something you’d like to see, send me a message and let me know!

Quotes from The Go Programming Language Specification Version of August 2, 2023

Share this

Related Content

Type parameters in method definitions

Yesterday we saw that when a method is defined on a generic type, the receiver must include type parameters. Now for all the relevant details: Method declarations … … Syntactically, this type parameter declaration looks like an instantiation of the receiver base type: the type arguments must be identifiers denoting the type parameters being declared, one for each type parameter of the receiver base type. The type parameter names do not need to match their corresponding parameter names in the receiver base type definition, and all non-blank parameter names must be unique in the receiver parameter section and the method signature.

Methods on generic types

Ready to dive back into generics, this time with regard to methods? Great, ‘cause here we go! Method declarations … If the receiver base type is a generic type, the receiver specification must declare corresponding type parameters for the method to use. Okay. Before we continue, let’s make sure we all know exactly what we’re talking about. What does it mean for a receiver base type to be a generic type?

Uniqueness of methods

Oops! Yesterday I live-streamed… to the wrong link! If you followed the link I shared yesterday, you were probably confused. But the good news is, you can still catch the replay. Method declarations … For a base type, the non-blank names of methods bound to it must be unique. If the base type is a struct type, the non-blank method and field names must be distinct. These uniqueness rules are quite predictible!