Now, let’s talk about what happens when you start embedding structs willy-nilly and the language starts promoting methods. It’s a fantastic feature until it isn’t. Go’s method promotion is like a well-meaning but overzealous intern: it tries to be helpful by making embedded fields’ methods available on the parent struct, but it has absolutely no sense of subtlety or conflict resolution.

When you embed a type, any methods defined on that type get “promoted” to the enclosing struct. This means you can call myParentStruct.TheEmbeddedStructsMethod() directly. It’s syntactic sugar, but it’s the good kind that makes your coffee taste better.

type Engine struct {
    Horsepower int
}

func (e Engine) Start() {
    fmt.Printf("Vroom! %d HP engaged.\n", e.Horsepower)
}

type Car struct {
    Model string
    Engine // Embedded, not a field: notice the lack of a name
}

func main() {
    myCar := Car{
        Model: "Thrustmaster",
        Engine: Engine{Horsepower: 255},
    }

    // This works because Engine's methods are promoted to Car
    myCar.Start() // Output: Vroom! 255 HP engaged.

    // But you can still be explicit, which is often clearer
    myCar.Engine.Start()
}

The Rules of Promotion

Promotion follows a simple, brutalist set of rules. A method is promoted if the embedded field’s name (the type name, by default) is unique and the method’s name isn’t already taken on the parent struct. It’s a first-come, first-served world in there. The compiler doesn’t send out memos to resolve disputes; it just throws a compile-time error the moment it detects an ambiguity. This is a good thing. It forces you to think about your design instead of letting a confusing mess compile.

The Name Collision Nightmare

Here’s where the “witty friend” gets to say “I told you so.” Let’s embed two types that both have a Start() method. What could possibly go wrong?

type DieselGenerator struct {
    KwOutput int
}

func (d DieselGenerator) Start() {
    fmt.Printf("Chugga-chugga! Generating %d kW.\n", d.KwOutput)
}

// Let's create a monstrosity: a Car with two embedded types that can both Start.
type HybridCar struct {
    Car             // Has a promoted Start() from Engine
    DieselGenerator // Also has a Start() method. Oh dear.
}

func main() {
    monstrosity := HybridCar{}
    monstrosity.Horsepower = 120
    monstrosity.KwOutput = 50

    // This is the moment of truth. This line WILL NOT COMPILE.
    monstrosity.Start()
}

The compiler looks at this and basically has a panic attack: ambiguous selector monstrosity.Start. It has no idea whether you want to start the Engine or the DieselGenerator, and it rightly refuses to guess. The designers made a very sane choice here: ambiguity is a hard error. The alternative—some arcane precedence rule you’d have to memorize—would be a nightmare.

Resolving the Collision

So your brilliant design has led to a compile-time error. Congratulations. Now you have to fix it. The solution is beautifully simple and explicit: you just qualify the method with the embedded field’s name.

func main() {
    monstrosity := HybridCar{}
    monstrosity.Horsepower = 120
    monstrosity.KwOutput = 50

    // No ambiguity here. We're being explicit.
    monstrosity.Car.Start()       // Output: Vroom! 120 HP engaged.
    monstrosity.DieselGenerator.Start() // Output: Chugga-chugga! Generating 50 kW.
}

This isn’t a workaround; it’s the correct way to handle the situation. It makes your code self-documenting and clear. The promotion feature is there for convenience in the non-ambiguous cases. The moment you introduce ambiguity, you lose the convenience and must be explicit. It’s the language’s way of telling you to clean up your design.

The Subtle Pitfall: Shadowing

Here’s a more insidious version of the problem. What if the parent struct itself has a method with the same name?

type BossyCar struct {
    Engine
}

func (b BossyCar) Start() {
    fmt.Println("No, I start MY way.")
}

func main() {
    bossy := BossyCar{Engine{Horsepower: 100}}
    bossy.Start()        // Output: No, I start MY way.
    bossy.Engine.Start() // Output: Vroom! 100 HP engaged.
}

In this case, the BossyCar’s own Start method shadows the promoted Engine.Start method. There’s no ambiguity for the compiler to error on because the outermost method wins. This is often the correct logic (you’re overriding the embedded type’s behavior), but if you did it by accident, it could be a incredibly confusing bug. The promoted method is still there, but you have to use the explicit path to reach it. The lesson here is to always be aware of the method set you’re building through embedding; it’s not magic, it’s just code organization with very specific rules.