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

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


Go 1.24.0 is here!

Go 1.24 is released! Yay! That means a short re-visit to the Go spec, as we look at things that have changed. From the release notes we see that there’s really only one significant change: Changes to the language¶ Go 1.24 now fully supports generic type aliases: a type alias may be parameterized like a defined type. See the language spec for details. For now, the feature can be disabled by setting GOEXPERIMENT=noaliastypeparams; but the aliastypeparams setting will be removed for Go 1.


Refresher on methods

In yesterday’s email, I failed to include the example included in the spec, which doesn’t make sense to include otherwise. I’ve updated the web version of the email in case you’re super curious to see what I left out. We’ve already covered these details, but they’re logically mentioned again in the section about function calls, so we’ll cover them here: Calls … A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m.


Method values, part 2

Today we finish up the spec’s section on method values, with some very non-surprising bits: Method values … As with selectors, a reference to a non-interface method with a value receiver using a pointer will automatically dereference that pointer: pt.Mv is equivalent to (*pt).Mv. As with method calls, a reference to a non-interface method with a pointer receiver using an addressable value will automatically take the address of that value: t.

Get daily content like this in your inbox!

Subscribe