Golang: Promoted methods, method sets and embedded types

In Go, interfaces are implemented implicitly by ensuring that the implementing type’s method set contains all the methods of the interface. That is, if a type A contains a method set that is a superset of interface I’s method set, then type A implements interface I.

This seems pretty straightforward, but can get a little convoluted when dealing with struct types that have embedded/anonymous fields.

Embedded in plain sight

In Go, a struct can have an embedded or anonymous field; the embedded type is specified just with the type name, and can either be a value type or a pointer to a such a type. For example, in the following code, the BossManager struct embeds the types Boss and Manager:

type Boss struct {}
func (b *Boss) AssignWork() {
	fmt.Println("Boss assigned work")
}

type Manager struct {}
func (m *Manager) PreparePowerPoint() {
	fmt.Println("PowerPoint prepared")
}

type BossManager struct {
	Boss
	Manager
}

Embedding is composition, not inheritance, but Go also does something called “promotion”, whereby the fields or methods of embedded types become available on the outer/embedding type. This provides a sort of automatic delegation and gives the pseudo-impression of “inheritance”, though it’s probably best not think of it that way. Here’s what the Go spec says about promotion:

A field or method f of an anonymous field in a struct x is called promoted if x.f is a legal selector that denotes that field or method f.

Promoted fields act like ordinary fields of a struct except that they cannot be used as field names in composite literals of the struct.

This means that for the above example, we can call the methods defined on the embedded types {Boss, Manager} as if they were defined on BossManager itself:

bm := BossManager{}

// Both methods (which use pointer receivers) have been promoted to BossManager.
bm.AssignWork() // "Boss assigned work"
bm.PreparePowerPoint() // "PowerPoint prepared"

However, these methods are not included in the method set for the value type BossManager. This is because they are defined with pointer receivers, not value receivers.

This is explained in the Go specification section on struct types, but also follows from the earlier section on method sets. I’ll summarize the salient points like so:

  1. The method set of an interface type is its interface
  2. The method set of any other type T consists of all methods declared with receiver type T, but does not include methods declared with a pointer receiver type *T.
  3. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T).

Point 2 is most important; if we think of the embedded types as having their methods defined on the outer type just as they were on the embedded type, the rules apply in the same way: Since {AssignWork(), PreparePowerPoint()} are defined with a pointer receiver, the value type BossManager doesn’t include them in its method set, even though they are still promoted methods and can be called on the type.

This also means if we define an interface like so:

// Define an interface that requires both methods.
type PromotionMaterial interface {
	AssignWork()
	PreparePowerPoint()
}

Then, the BossManager struct, as defined above, does not implement this interface. However, the pointer type *BossManager does, because its method set does include the promoted methods with pointer receivers. The Go specification on structs summarizes this as follows:

If S contains an anonymous field T, the method sets of S and *S both include promoted methods with receiver T. The method set of *S also includes promoted methods with receiver *T.
If S contains an anonymous field *T, the method sets of S and *S both include promoted methods with receiver T or *T.

Using embedded pointer types

An alternative is using an embedded pointer type. This would allow the outer/embedding type’s method set to contain the methods on the embedded type that use either a value or pointer receiver. For example:

type BossManagerUsingPointers struct {
	*Boss
	*Manager
}

This value type would implement the interface PromotionMaterial (defined previously) and its associated pointer type would also implement the interface.

A simple example

Since the concepts can be a bit confusing at first, I put together a small example based on the code snippets above. It’s available on the Go Playground or below.

package main

import "fmt"

type Boss struct {}
func (b *Boss) AssignWork() {
	fmt.Println("Boss assigned work")
}

type Manager struct {}
func (m *Manager) PreparePowerPoint() {
	fmt.Println("PowerPoint prepared")
}

type BossManager struct {
	Boss
	Manager
}
type BossManagerUsingPointers struct {
	*Boss
	*Manager
}


// Define an interface that requires both methods.
type PromotionMaterial interface {
	AssignWork()
	PreparePowerPoint()
}

func promote(pm PromotionMaterial) {
	fmt.Println("Promoted a person with promise.")
}

func main() {
	bm := BossManager{}
	
	// Both methods (which use pointer receivers) have been promoted to BossManager.
	bm.AssignWork() // "Boss assigned work"
	bm.PreparePowerPoint() // "PowerPoint prepared"
	
	// However, the method set of BossManager does not include either method because:
	// 1) {Boss, Manager} are embedded as value types, not pointer types.
	// 2) This makes it so that only the pointer type *BossManager includes both methods 
	// in its method set, thus making it implement interface PromotionMaterial.

	// promote(bm)	// Would fail with: cannot use bm (type BossManager) as type PromotionMaterial in argument to promote:
			// BossManager does not implement PromotionMaterial (AssignWork method has pointer receiver)
			// This would work if {Boss, Manager} were embedded as pointer types.
	
	promote(&bm) // Works
	
	// Lets use the struct with the embedded pointer types:
	bm2 := BossManagerUsingPointers{}
	bm2.AssignWork() // "Boss assigned work"
	bm2.PreparePowerPoint() // "PowerPoint prepared"
	promote(bm2) // Works
	promote(&bm2) 	// Also works, since both BossManagerUsingPointers (value type) and *BossManagerUsingPointers (pointer type)
			// method sets include both methods.
}

Comments for this entry are closed

But feel free to indulge in some introspective thought.