Saturday, August 15, 2026
Why Is My Embedded Field's Method Not Being Called?
Posted by

Why Is My Embedded Field's Method Not Being Called (Method Promotion Confusion)
Because promotion is a selector shortcut, not a vtable. outer.Method() is rewritten to outer.Inner.Method() when Inner has Method and outer does not. The receiver is still Inner. If Inner.Method calls m.other(), that is Inner.other, never a method you wrote on the outer type. If the outer type also declares Method, yours wins on outer.Method() and the embedded one is only outer.Inner.Method().
The failure
People coming from subclasses write this and expect Start on Car to run when Engine.boot calls e.Start():
package main
import "fmt"
type Engine struct{}
func (e Engine) Start() { fmt.Println("engine") }
func (e Engine) Boot() { e.Start() } // always Engine.Start
type Car struct {
Engine
}
func (c Car) Start() { fmt.Println("car") }
func main() {
var c Car
c.Start() // car — outer shadows
c.Boot() // engine — Boot's receiver is Engine
}
c.Boot() is promoted. Inside Boot, e is an Engine. There is no link back to Car.
The other common miss: two embeds at the same depth both have ID. x.ID does not compile. The compiler will not pick one.
type A struct{ ID int }
type B struct{ ID int }
type Both struct {
A
B
}
// both.ID // ambiguous selector
fmt.Println(both.A.ID, both.B.ID)
A nil embedded pointer is a third way the "method" appears missing: it panics instead.
type Inner struct{ N int }
type Outer struct{ *Inner }
var o Outer
fmt.Println(o.N) // panic: nil pointer dereference
Why this happens
The compiler searches by depth. Depth 0 (declared on the outer type) wins. Then depth 1, then deeper. Two matches at the same depth are an error. That search is compile-time. Nothing looks at the runtime type of the outer value from inside an inner method.
Interface satisfaction follows the same names. If Car has Start, Car implements an interface with Start() via Car's method. The embedded Engine.Start is not the one the interface calls. If Car does not declare Start, the promoted method is the one that satisfies the interface — still with Engine as the receiver.
The fix
If you need the outer type in the call, write the method on the outer type and call the inner one explicitly:
func (c Car) Boot() {
fmt.Println("car wrapping")
c.Engine.Boot()
}
If you wanted the embedded method, do not declare a same-named method on the outer type, or call c.Engine.Start().
If two embeds collide, always use the typed path (x.A.ID). Or give the outer type its own field so it shadows both.
If you embed *Inner, construct it: Outer{Inner: &Inner{N: 1}}.