XはYを実装していません(…メソッドにはポインターレシーバーがあります)[終了]


201

この「XはYを実装しない(...メソッドにはポインターレシーバーがある)」というQ&Aはすでにいくつかありますが、私には、彼らはさまざまなことについて話しているようで、私の特定のケースには当てはまりません。

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

つまり、問題を回避する方法、およびそれが発生した場合、可能性は何ですか?どうも。

回答:


365

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

例を見てみましょう:

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である*MyTypeStringer

s = &m
fmt.Println(s)

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

something

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

  • 割り当てられている(または渡された、または変換された)非ポインターコンクリートタイプの値
  • 割り当てられる(または渡される、または変換される)インターフェイスの種類
  • 具象型にはインターフェースの必須メソッドがありますが、ポインターレシーバーがあります

問題を解決する可能性:

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

構造体と埋め込み

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

type MyType2 struct {
    MyType
}

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

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

繰り返しますが、コンパイル時エラーです。メソッドセットにMyType2String()embedded のメソッドが含まれておらずMyType、メソッドセットだけが含まれている*MyType2ため、次のように機能します(Go Playgroundで試してください)。

var s Stringer
s = &m2

非ポインター*MyTypeのみを埋め込み、使用する場合は、それを機能させることもできます MyType2Go 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匿名フィールドが含まれている場合TSおよびのメソッドセットには、*Sレシーバ付きの昇格されたメソッドが含まれますT。のメソッドセットには*S、レシーバーでプロモートされたメソッドも含まれ*Tます。
  • S匿名フィールドが含まれている場合*TSおよびのメソッドセットには、*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))

非常に包括的な答えをありがとう。不思議なことに私はSO通知を受け取っていなかったので、遅れて応答して申し訳ありません。私が検索した1つのケースの答えは、「メンバー関数」は、すべてのポインター型、たとえば「func (m *MyType)」または「なし」のいずれかである必要があるというものでした。そうですか?func (m *MyType)&など、異なるタイプの「メンバー関数」を混在させることはできますfunc (m MyType)か?
xpt '30 / 11/16

3
@xptポインタレシーバと非ポインタレシーバを混在させることができます。すべてを同じにする必要はありません。ポインターレシーバーを持つ19のメソッドがあり、非ポインターレシーバーを持つメソッドを作成するのは変です。また、混合を開始すると、どのメソッドがどの型のメソッドセットの一部であるかを追跡するのが難しくなります。この回答の詳細:Golangでの値レシーバーとポインターレシーバー
icza 2016年

値メソッドを使用するようにMyType変更できない場合、「注:」の最後に記載されている問題を実際にどのように解決しますか?MyType私はこのようなものを試しました(&i).(*Stringer)が、うまくいきません。それも可能ですか?
JoelEdström2017

1
@JoelEdströmはい、それは可能ですが、ほとんど意味がありません。たとえば、非ポインター型の値をタイプアサートして変数に格納するx := i.(MyType)ことができます(例:)。その後、ポインターレシーバーを使用してメソッドを呼び出すことができます。たとえばi.String()(&i).String()変数はアドレス指定可能であるため成功するための省略形です。しかし、値(ポイントされた値)を変更するポインターメソッドは、インターフェイス値にラップされた値には反映されません。そのため、ほとんど意味がありません。
icza 2017

1
@DeepNightTwoのメソッドは、アドレス指定できない可能性が*TあるSためS(たとえば、関数の戻り値やマップのインデックス付けの結果)、コピーのみが存在/受信されることが多く、そのアドレスの取得が許可されている場合はメソッドのメソッドセットに含まれません。ポインターレシーバーを使用すると、コピーのみを変更できます(元のものが変更されていると想定する場合の混乱)。例については、この回答を参照してください:リフレクションSetStringの使用
icza 2017

33

短くするために、このコードがあり、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の回答を参照してください


6
断然これは理解するのが最も簡単なコメントでした。そして、直接私が直面した問題...解決
Maxs728

@ Maxs728同意。多くの囲碁の問題への回答では非常に珍しい。
-milosmns

6

この種のことが起こっているのを見たもう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)
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.