Code Fix

上級

埋め込み構造体のメソッドが外側の同名メソッドにシャドーイングされる

構造体に別の構造体を埋め込むと、埋め込み側のメソッドが昇格して呼び出せるようになります。しかし外側の構造体が同名のメソッドを定義していると、そちらが優先され埋め込み側のメソッドは隠れてしまいます。

なぜエラーが出ないのか

出力: ...
(エラーなし)
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  }
出力: a vehicle

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  }
出力: shape

埋め込み側を経由して呼び出すと、外側の型が持つ独自の実装を素通りしてしまいます。

直し方: 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  }
出力: staff

外側の型が同名メソッドを再定義した時点で、そのメソッドを呼ぶ通常の経路は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パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)