回答:
このコンパイル時エラーは、具象型をインターフェイス型に割り当てたり、渡したり(または変換)しようとしたときに発生します。型自体はインターフェイスを実装せず、型へのポインタのみを実装します。
例を見てみましょう:
type Stringer interface {
String() string
}
type MyType struct {
value string
}
func (m *MyType) String() string { return m.value }
Stringer
インターフェイスタイプは、唯一の方法がありますString()
。インターフェイス値に格納されているすべての値には、Stringer
このメソッドが必要です。また、作成したMyType
、と私たちはメソッドを作成しMyType.String()
て、ポインタの受信機。つまり、String()
メソッドはタイプのメソッドセットに含まれますが、のメソッドセットには含まれ*MyType
ませんMyType
。
MyType
タイプの変数にの値を割り当てようとするとStringer
、問題のエラーが発生します。
m := MyType{value: "something"}
var s Stringer
s = m // cannot use m (type MyType) as type Stringer in assignment:
// MyType does not implement Stringer (String method has pointer receiver)
私たちは、型の値を代入しようとした場合でも、すべてがOKである*MyType
にStringer
:
s = &m
fmt.Println(s)
そして、期待される結果が得られます(Go Playgroundで試してください)。
something
したがって、このコンパイル時エラーを取得するための要件:
問題を解決する可能性:
構造体と埋め込みを使用する場合、多くの場合、インターフェースを実装する(メソッド実装を提供する)のは「あなた」ではなく、に埋め込む型ですstruct
。この例のように:
type MyType2 struct {
MyType
}
m := MyType{value: "something"}
m2 := MyType2{MyType: m}
var s Stringer
s = m2 // Compile-time error again
繰り返しますが、コンパイル時エラーです。メソッドセットにMyType2
はString()
embedded のメソッドが含まれておらずMyType
、メソッドセットだけが含まれている*MyType2
ため、次のように機能します(Go Playgroundで試してください)。
var s Stringer
s = &m2
非ポインター*MyType
のみを埋め込み、使用する場合は、それを機能させることもできます MyType2
(Go Playgroundで試してください)。
type MyType2 struct {
*MyType
}
m := MyType{value: "something"}
m2 := MyType2{MyType: &m}
var s Stringer
s = m2
また、埋め込むもの(MyType
または*MyType
)を問わず、ポインタを使用する*MyType2
と、常に機能します(Go Playgroundで試してください)。
type MyType2 struct {
*MyType
}
m := MyType{value: "something"}
m2 := MyType2{MyType: &m}
var s Stringer
s = &m2
仕様の関連セクション(セクション構造体タイプから):
構造体タイプ
S
と名前が付けられたタイプを指定するとT
、プロモートされたメソッドは、次のように構造体のメソッドセットに含まれます。
S
匿名フィールドが含まれている場合T
、S
およびのメソッドセットには、*S
レシーバ付きの昇格されたメソッドが含まれますT
。のメソッドセットには*S
、レシーバーでプロモートされたメソッドも含まれ*T
ます。- に
S
匿名フィールドが含まれている場合*T
、S
およびのメソッドセットには、*S
レシーバーT
またはを含むプロモートメソッドが含まれます*T
。
したがって、言い換えると、非ポインター型を埋め込む場合、非ポインター埋め込みのメソッドセットは、(埋め込み型から)非ポインターレシーバーを持つメソッドのみを取得します。
ポインター型を埋め込む場合、非ポインター埋め込みのメソッドセットは、(埋め込み型から)ポインターレシーバーと非ポインターレシーバーの両方を持つメソッドを取得します。
埋め込み型へのポインター値を使用する場合、埋め込み型がポインターであるかどうかに関係なく、埋め込み型へのポインターのメソッドセットは常に、ポインターと非ポインターレシーバーの両方を持つメソッドを(埋め込み型から)取得します。
注意:
非常によく似たケースがあります。つまり、の値をラップするインターフェース値があり、そこから別のインターフェース値をassertMyType
と入力しようとした場合ですStringer
。この場合、アサーションは上記の理由で保持されませんが、少し異なる実行時エラーが発生します。
m := MyType{value: "something"}
var i interface{} = m
fmt.Println(i.(Stringer))
ランタイムパニック(Go Playgroundで試してください):
panic: interface conversion: main.MyType is not main.Stringer:
missing method String
アサート型の代わりに変換しようとすると、私たちが話しているコンパイル時エラーが発生します。
m := MyType{value: "something"}
fmt.Println(Stringer(m))
MyType
変更できない場合、「注:」の最後に記載されている問題を実際にどのように解決しますか?MyType
私はこのようなものを試しました(&i).(*Stringer)
が、うまくいきません。それも可能ですか?
x := i.(MyType)
ことができます(例:)。その後、ポインターレシーバーを使用してメソッドを呼び出すことができます。たとえばi.String()
、(&i).String()
変数はアドレス指定可能であるため成功するための省略形です。しかし、値(ポイントされた値)を変更するポインターメソッドは、インターフェイス値にラップされた値には反映されません。そのため、ほとんど意味がありません。
*T
あるS
ためS
(たとえば、関数の戻り値やマップのインデックス付けの結果)、コピーのみが存在/受信されることが多く、そのアドレスの取得が許可されている場合はメソッドのメソッドセットに含まれません。ポインターレシーバーを使用すると、コピーのみを変更できます(元のものが変更されていると想定する場合の混乱)。例については、この回答を参照してください:リフレクションSetStringの使用。
短くするために、このコードがあり、Loaderインターフェースとこのインターフェースを実装するWebLoaderがあるとします。
package main
import "fmt"
// Loader defines a content loader
type Loader interface {
Load(src string) string
}
// WebLoader is a web content loader
type WebLoader struct{}
// Load loads the content of a page
func (w *WebLoader) Load(src string) string {
return fmt.Sprintf("I loaded this page %s", src)
}
func main() {
webLoader := WebLoader{}
loadContent(webLoader)
}
func loadContent(loader Loader) {
loader.Load("google.com")
}
したがって、このコードはあなたにこのコンパイル時エラーを与えます
./main.go:20:13:loadContentへの引数の型ローダーとしてwebLoader(型WebLoader)を使用できません:WebLoaderはローダーを実装していません(Loadメソッドにはポインターレシーバーがあります)
したがってwebLoader := WebLoader{}
、次のように変更するだけです。
webLoader := &WebLoader{}
func (w *WebLoader) Load
ポインタレシーバーを受け入れるようにこの関数を定義したので、なぜ修正されるのでしょうか。詳細については、@ iczaおよび@karoraの回答を参照してください
この種のことが起こっているのを見たもう1つのケースは、一部のメソッドが内部値を変更し、他のメソッドは変更しないインターフェースを作成する場合です。
type GetterSetter interface {
GetVal() int
SetVal(x int) int
}
このインターフェイスを実装するものは次のようになります。
type MyTypeA struct {
a int
}
func (m MyTypeA) GetVal() int {
return a
}
func (m *MyTypeA) SetVal(newVal int) int {
int oldVal = m.a
m.a = newVal
return oldVal
}
したがって、実装型には、ポインターレシーバーであるメソッドとそうでないメソッドが含まれている可能性が高く、ゲッターセッターであるこれらのさまざまなものがさまざまであるため、テストですべてが期待どおりに動作していることを確認したいと思います。
私がこのようなことをするなら:
myTypeInstance := MyType{ 7 }
... maybe some code doing other stuff ...
var f interface{} = myTypeInstance
_, ok := f.(GetterSetter)
if !ok {
t.Fail()
}
次に、前述の「XはYを実装しない(Zメソッドにはポインターレシーバーがある)」エラー(コンパイル時エラーであるため)は表示されませんが、テストが失敗した理由を正確に追跡するのは悪い日になります。 。
代わりに、次のようなポインタを使用して型チェックを行う必要があります。
var f interface{} = new(&MyTypeA)
...
または:
myTypeInstance := MyType{ 7 }
var f interface{} = &myTypeInstance
...
その後、すべてがテストに満足します!
ちょっと待って!私のコードでは、おそらくどこかにGetterSetterを受け入れるメソッドがあります。
func SomeStuff(g GetterSetter, x int) int {
if x > 10 {
return g.GetVal() + 1
}
return g.GetVal()
}
これらのメソッドを別のタイプメソッドの内部から呼び出すと、エラーが発生します。
func (m MyTypeA) OtherThing(x int) {
SomeStuff(m, x)
}
次のいずれかの呼び出しが機能します。
func (m *MyTypeA) OtherThing(x int) {
SomeStuff(m, x)
}
func (m MyTypeA) OtherThing(x int) {
SomeStuff(&m, x)
}
func (m *MyType)
」または「なし」のいずれかである必要があるというものでした。そうですか?func (m *MyType)
&など、異なるタイプの「メンバー関数」を混在させることはできますfunc (m MyType)
か?