埋め込み構造体のメソッドが外側の同名メソッドにシャドーイングされる
構造体に別の構造体を埋め込むと、埋め込み側のメソッドが昇格して呼び出せるようになります。しかし外側の構造体が同名のメソッドを定義していると、そちらが優先され埋め込み側のメソッドは隠れてしまいます。
なぜエラーが出ないのか
出力: ...
(エラーなし)- Goは何も報告しません。コンパイルも実行も正常に終わるコードだからです
出力: ...- 実際の出力 — 期待した結果と食い違っている箇所
見つけ方- エラーが出ないので、出力を目で確かめるしかありません。この種の誤りが最も発見が遅れます
このエラーが出る典型パターン
パターン1
1 package main 2 3 import "fmt" 4 5 type Animal struct{} 6 7 func (a Animal) Sound() string { return "..." } 8 9 type Dog struct{ Animal } 10 11 func (d Dog) Sound() string { return "Woof" } 12 13 func main() { 14 d := Dog{} 15 fmt.Println(d.Animal.Sound() ) ^ 16 }
DogはSoundを独自に定義しているため、d.Sound()と書くだけでそちらが優先されます。d.Animal.Sound()と明示的に書くと、埋め込み側の実装を直接呼び出してしまいます。
直し方: d.Animal.Sound() を d.Sound() にします。
パターン2
1 package main 2 3 import "fmt" 4 5 type Vehicle struct{} 6 7 func (v Vehicle) Describe() string { return "a vehicle" } 8 9 type Car struct{ Vehicle } 10 11 func (c Car) Describe() string { return "a car" } 12 13 func main() { 14 c := Car{} 15 fmt.Println(c.Vehicle.Describe() ) ^ 16 }
Carが自分のDescribeを持っているので、c.Describe()を呼べば意図通り「a car」が得られます。
直し方: c.Vehicle.Describe() を c.Describe() にします。
パターン3
1 package main 2 3 import "fmt" 4 5 type Shape struct{} 6 7 func (s Shape) Name() string { return "shape" } 8 9 type Circle struct{ Shape } 10 11 func (c Circle) Name() string { return "circle" } 12 13 func main() { 14 c := Circle{} 15 fmt.Println(c.Shape.Name() ) ^ 16 }
埋め込み側を経由して呼び出すと、外側の型が持つ独自の実装を素通りしてしまいます。
直し方: c.Shape.Name() を c.Name() にします。
パターン4
1 package main 2 3 import "fmt" 4 5 type Employee struct{} 6 7 func (e Employee) Role() string { return "staff" } 8 9 type Manager struct{ Employee } 10 11 func (m Manager) Role() string { return "manager" } 12 13 func main() { 14 m := Manager{} 15 fmt.Println(m.Employee.Role() ) ^ 16 }
外側の型が同名メソッドを再定義した時点で、そのメソッドを呼ぶ通常の経路はm.Role()だけで十分です。
直し方: m.Employee.Role() を m.Role() にします。
パターン5
1 package main 2 3 import "fmt" 4 5 type Instrument struct{} 6 7 func (i Instrument) Play() string { return "..." } 8 9 type Piano struct{ Instrument } 10 11 func (p Piano) Play() string { return "plink" } 12 13 func main() { 14 p := Piano{} 15 fmt.Println(p.Instrument.Play() ) ^ 16 }
フィールド名を経由した呼び出しは、埋め込み側の実装をピンポイントで呼びたい特殊なケース以外では不要です。
直し方: p.Instrument.Play() を p.Play() にします。
よくある誤解
「埋め込んだ型のメソッドを呼ぶには、d.Animal.Sound()のようにフィールド名を経由して明示的に呼び出す必要があるはず」という思い込みは誤りです。外側の型(Dog)が同名メソッドを定義していれば、そちらが自動的に優先されるため、d.Sound()と書くだけで正しく解決されます。逆にd.Animal.Sound()と明示的に書いてしまうと、意図せず埋め込み側の実装を直接呼び出すことになり、期待と違う結果になります。
まとめ
埋め込み構造体のメソッドが外側の同名メソッドにシャドーイングされるは上級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。