Embedding T vs *T: how does each affect the method set of the outer struct?
Question 88HardGo 1.22 to 1.25
For a struct S:
- If
SembedsT, the method sets ofSand*Sinclude the promoted methods with receiverT. Only*Salso includes those with receiver*T. - If
Sembeds*T, the method sets ofSand*Sboth include the promoted methods with receiverTand*T.
type T struct{}
func (T) A() {}
func (*T) B() {}
type ByVal struct{ T }
type ByPtr struct{ *T }
type AB interface{ A(); B() }
// var _ AB = ByVal{} // error: ByVal does not implement AB (method B has pointer receiver)
var _ AB = &ByVal{} // OK
var _ AB = ByPtr{} // OK: B reachable via the embedded pointer
var _ AB = &ByPtr{} // OK
func main() {
var p ByPtr // p.T == nil
p.A() // panic: nil pointer dereference (A needs *p.T copied as value)
p.B() // fine if B does not touch fields; receiver is nil *T
}
The trade-offs:
- Embedding a pointer lets several outer values share one inner value, lets you swap the inner value at runtime, and gives the value type the full method set.
- The cost is that the zero value of the outer struct holds a nil pointer, which leads to panics. Copying the outer struct also shares state, which may be surprising.
- Embedding by value makes the zero value usable, as with
struct{ sync.Mutex }.
More on Interfaces, Methods & Embedding
- Q86Is struct embedding inheritance? How does method promotion work?
- Q87What does this print? (no virtual dispatch with embedding)
- Q89What happens when two embedded types provide the same field or method name?
- Q90What is interface embedding, and what changed in Go 1.14 about overlapping methods?
- Q91Why would you embed an interface inside a struct? What is the danger?
- Q92When should you use
any(interface{}), and when are generics the better choice?