Go

Embedding is not inheritance: what does this print, and which line does not compile?

Question 513HardGo 1.22 to 1.25
type Base struct{}

func (Base) Name() string    { return "base" }
func (b Base) Greet() string { return "hi " + b.Name() }

type Derived struct{ Base }

func (Derived) Name() string { return "derived" }

type A struct{}
type B struct{}

func (A) Hello() string { return "A" }
func (B) Hello() string { return "B" }

type C struct {
    A
    B
}

func main() {
    d := Derived{}
    fmt.Println(d.Name(), d.Greet(), d.Base.Name())

    var c C
    fmt.Println(c.A.Hello())
    // fmt.Println(c.Hello()) // compile error: ambiguous selector c.Hello

    type P struct{ *A }
    var p P
    fmt.Println(p.Hello()) // panics
}

It prints derived hi base base, then A, then panics with a nil pointer dereference.

d.Greet() is promoted from Base, and it runs with a Base receiver. Inside it, b.Name() is resolved statically to Base.Name. Go has no virtual dispatch through embedding, so the "override" in Derived is never called from Base's methods. If you need that, use an interface field or pass the behaviour in as a function.

When two embedded types at the same depth both provide Hello, neither is promoted. The struct still compiles, and the error appears only when someone writes c.Hello(). C also does not satisfy interface{ Hello() string }. A method declared on the outer type, or a field at a shallower depth, would win instead. This means adding a method to an embedded library type can silently break or change your code.

Embedding a pointer promotes its methods too. With a nil *A, the value-receiver Hello has to dereference the pointer to copy the receiver, so the call panics. Code-review angle: embedding a type such as sync.Mutex in an exported struct also exports Lock and Unlock as part of your API.

More on Tricky Output & Code-Review Puzzles

All 38 Tricky Output & Code-Review Puzzles questions