1. ホーム
  2. pointers

[解決済み】XがYを実装していない(...メソッドがポインタのレシーバを持つ)

2022-03-30 10:13:16

質問

この件に関するQ&Aはすでにいくつかあります。 XはYを実装していない(...メソッドにポインタレシーバーがある)。 しかし、私には、それらは異なることを話しているように見え、私の特定のケースには当てはまらないように思えます。

このエラーが発生するケースはいくつかあるようなのですが、どなたかまとめていただけませんか?

つまり、問題を回避する方法と、問題が発生した場合、どのような可能性があるかということです。Thx.

解決方法は?

このコンパイル時のエラーは、以下のような場合に発生します。 コンクリート 型がインターフェイスを実装していない場合。 型へのポインタ .

簡単な要約です。 An 割り当て は、代入される値が代入先のインタフェースを実装している場合に有効です。もし、その メソッドセット はインターフェイスのスーパーセットです。ポインタ型のメソッドセットには 両方とも ポインタのレシーバと非ポインタのレシーバがあります。非ポインタ型のメソッド群 のみ には、非ポインタのレシーバを持つメソッドが含まれます。

例を見てみましょう。

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

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

Stringer インタフェース型は、1つのメソッドのみを持ちます。 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)

しかし、このような型に値を割り当てる場合は、すべて問題ありません。 *MyTypeStringer :

s = &m
fmt.Println(s)

そして、期待通りの結果が得られます(試しに 囲碁プレイグラウンド ):

something

つまり、このコンパイルタイムエラーが発生するための条件です。

  • の値 ポインタでない 代入される(あるいは渡される、あるいは変換される)具体的な型
  • 代入される(あるいは渡される、あるいは変換される)インターフェース型
  • 具象型は、インターフェースの必須メソッドを持つが、そのメソッドに ポインタレシーバ

問題解決のための可能性

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

構造体とエンベッディング

を使用する場合 構造体とエンベッディング インターフェイスを実装する(メソッドの実装を提供する)のは、多くの場合、quot;you" ではなく、あなたの struct . この例のように。

type MyType2 struct {
    MyType
}

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

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

のメソッドセットであるため、ここでもコンパイル時のエラーとなります。 MyType2 には String() メソッドを埋め込むことができます。 MyType のメソッドセットのみです。 *MyType2 ということで、以下のように動作します(試しに 囲碁の遊び場 ):

var s Stringer
s = &m2

を埋め込めば、うまくいくはずです。 *MyType のみを使用し 非ポインタ MyType2 (で試してみてください)。 囲碁の遊び場 ):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = m2

また、何を埋め込んでも( MyType または *MyType ) を使用する場合、ポインタ *MyType2 で試してみてください。 囲碁の遊び場 ):

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 を実行しようとすると タイプアサート 別のインターフェースの値を取得します。 Stringer . この場合、上記の理由によりアサーションは成立しませんが、若干異なるランタイムエラーが発生します。

m := MyType{value: "something"}

var i interface{} = m
fmt.Println(i.(Stringer))

ランタイムパニック(で試してみてください。 囲碁プレイグラウンド ):

panic: interface conversion: main.MyType is not main.Stringer:
    missing method String

タイプアサートの代わりに変換しようとすると、我々が話しているようなコンパイル時エラーが発生します。

m := MyType{value: "something"}

fmt.Println(Stringer(m))