web-dev-qa-db-ja.com

XはYを実装していません(...メソッドはポインタレシーバを持っています)

"XはYを実装していません(...メソッドはポインタレシーバーを持っています)"ということに関してすでにいくつかのQ&Aがありますが、私には、違うことについて話しているようです私の特定の場合.

それで、質問を非常に具体的にする代わりに、私はそれを広く抽象的にしています - このエラーを起こすことができるいくつかの異なるケースがあるようです、誰かがそれを要約してもらえますか?

つまり、問題を回避する方法、そして問題が発生した場合、どのような可能性がありますか。 THX。

127
xpt

このコンパイル時エラーは、インターフェイスタイプにconcreteタイプを割り当てたり、渡したり(または変換)しようとすると発生します。また、タイプ自体はインターフェースを実装せず、タイプへのポインターのみを実装します。

例を見てみましょう:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

func (m *MyType) String() string { return m.value }

Stringerインターフェイスタイプには、String()という1つのメソッドのみがあります。インターフェイス値Stringerに保存される値には、このメソッドが必要です。また、MyTypeを作成し、pointerレシーバーで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)

しかし、タイプ*MyTypeの値をStringerに割り当てようとしても問題ありません。

s = &m
fmt.Println(s)

そして、期待される結果が得られます( Go Playground で試してください):

something

したがって、このコンパイル時エラーを取得するための要件:

  • 値がnon-pointer割り当てられている(または渡された、または変換された)具象型
  • 割り当てられる(または渡される、または変換される)インターフェイスタイプ
  • 具象型には、インターフェースに必要なメソッドがありますが、pointer receiver

問題を解決する可能性:

  • 値へのポインターを使用する必要があり、そのメソッドセットにはポインターレシーバーのメソッドが含まれます。
  • または、レシーバータイプをnon-pointerに変更する必要があります。そのため、非ポインターコンクリートタイプのメソッドセットにもメソッドが含まれます(したがって、インターフェイスを満たします)。メソッドが値を変更する必要がある場合、非ポインターレシーバーはオプションではないため、これは実行可能または実行不可能な場合があります。

構造と埋め込み

structs and embedding を使用する場合、多くの場合、インターフェイスを実装する(メソッド実装を提供する)のは「あなた」ではなく、structに埋め込むタイプです。この例のように:

type MyType2 struct {
    MyType
}

m := MyType{value: "something"}
m2 := MyType2{MyType: m}

var s Stringer
s = m2 // Compile-time error again

繰り返しますが、コンパイル時エラー。MyType2のメソッドセットには埋め込みMyTypeString()メソッドが含まれず、*MyType2のメソッドセットのみが含まれるため、次のようになります。動作します( Go Playground で試してください):

var s Stringer
s = &m2

*MyTypeを埋め込み、non-pointerMyType2のみを使用して動作させることもできます( 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の昇格されたメソッドが含まれます。

つまり、非ポインター型を埋め込むと、非ポインター埋め込みのメソッドセットは、非埋め込み型レシーバーのメソッドのみを(埋め込み型から)取得します。

ポインター型を埋め込むと、非ポインター埋め込みのメソッドセットは、(埋め込み型から)ポインターと非ポインターの両方のレシーバーを持つメソッドを取得します。

埋め込み型へのポインター値を使用する場合、埋め込み型がポインターであるかどうかに関係なく、埋め込み型へのポインターのメソッドセットは常に(埋め込み型から)ポインターと非ポインターの両方のレシーバーを持つメソッドを取得します。

注:

非常によく似たケースがあります。つまり、MyTypeの値をラップするインターフェイス値があり、 type assert 別のインターフェイス値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))
269
icza

短くするために、このコードがあり、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(type WebLoader)を引数として使用することはできません:WebLoaderはLoaderを実装していません(Loadメソッドはポインタレシーバを持っています)

だからあなたがする必要があるのはwebLoader := WebLoader{}を次のように変更することです。

webLoader := &WebLoader{} 

それで、なぜあなたはこの関数func (w *WebLoader) Loadをポインタレシーバーを受け入れるために定義するのでそれが修正されるのか。もっと詳しい説明は@iczaと@karoraの回答を読んでください

14
Saman Shafigh

このようなことが起こっているのを見たときのもう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
}

そのため、実装型には、ポインター受信側のメソッドとそうでないメソッドがある可能性があります。これらはGetterSettersというさまざまなものが多数あるため、テストで期待どおりに動作することを確認します。

私がこのようなことをするとしたら:

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)
}
4
karora