「<type>はインターフェースではなくインターフェースへのポインタです」混乱


102

仲間の開発者各位

私には少し奇妙に思えるこの問題があります。次のコードスニペットをご覧ください。

package coreinterfaces

type FilterInterface interface {
    Filter(s *string) bool
}

type FieldFilter struct {
    Key string
    Val string
}

func (ff *FieldFilter) Filter(s *string) bool {
    // Some code
}

type FilterMapInterface interface {
    AddFilter(f *FilterInterface) uuid.UUID     
    RemoveFilter(i uuid.UUID)                   
    GetFilterByID(i uuid.UUID) *FilterInterface
}

type FilterMap struct {
    mutex   sync.Mutex
    Filters map[uuid.UUID]FilterInterface
}

func (fp *FilterMap) AddFilter(f *FilterInterface) uuid.UUID {
    // Some code
}

func (fp *FilterMap) RemoveFilter(i uuid.UUID) {
    // Some code
}

func (fp *FilterMap) GetFilterByID(i uuid.UUID) *FilterInterface {
    // Some code
}

他のいくつかのパッケージでは、次のコードがあります。

func DoFilter() {
    fieldfilter := &coreinterfaces.FieldFilter{Key: "app", Val: "152511"}
    filtermap := &coreinterfaces.FilterMap{}
    _ = filtermap.AddFilter(fieldfilter) // <--- Exception is raised here
}

ランタイムは上記の行を受け入れません。

「fieldint.AddFilterへの引数で* coreinterfaces.FilterInterfaceタイプとしてフィールドフィルター(* coreinterfaces.FieldFilterタイプ)を使用することはできません:* coreinterfaces.FilterInterfaceは、インターフェイスではなくインターフェイスへのポインターです」

ただし、コードを次のように変更すると、

func DoBid() error {
    bs := string(b)
    var ifilterfield coreinterfaces.FilterInterface
    fieldfilter := &coreinterfaces.FieldFilter{Key: "app", Val: "152511"}
    ifilterfield = fieldfilter
    filtermap := &coreinterfaces.FilterMap{}
    _ = filtermap.AddFilter(&ifilterfield)
}

すべてが大丈夫であり、アプリケーションをデバッグするとき、それは本当に含むようです

私はこのトピックについて少し混乱しています。他のブログの記事を見て、スタックオーバーフロースレッドが(例えば-このまったく同じ問題について議論するとき、この、または これを)この例外を発生させる第一のスニペットは動作する必要があり、fieldfilterとFieldMapの両方がむしろの値より、インターフェースへのポインタとして初期化されるのでインターフェース。FieldInterfaceを宣言せずにそのインターフェースの実装を割り当てないようにするために変更する必要がある、実際にここで発生することについて頭を抱えることができませんでした。これにはエレガントな方法が必要です。


変更するとき* FilterInterfaceFilterInterfaceライン_ = filtermap.AddFilter(fieldfilter):今これがレイズcoreinterfaces.FieldFilterはcoreinterfaces.FilterInterfaceを実装していない(フィルタメソッドがポインタ受信機を有する):filtermap.AddFilterに引数に型coreinterfaces.FilterInterfaceとして(型coreinterfaces.FieldFilter)fieldfilter使用できない変更する場合が_ = filtermap.AddFilter(&fieldfilter)それへの行は動作します。そこで何が起こるの?何故ですか?
0rka

2
インターフェイスを実装するメソッドにはポインターレシーバーがあるためです。値を渡しても、インターフェースは実装されません。メソッドが適用されるため、ポインタを渡します。一般的に言って、インターフェースを扱う場合、インターフェースを期待する関数へのポインタを構造体に渡します。どのようなシナリオでも、インターフェイスへのポインタはほとんど必要ありません。
エイドリアン

1
私はあなたの要点を理解しましたが、パラメーター値をから* FilterInterfaceこのインターフェイスを実装する構造体に変更することで、インターフェイスを関数に渡すという考えを壊します。私が達成したかったことは、渡した構造体にバインドされているのではなく、使用したいインターフェイスを実装するすべての構造体です。あなたがより効率的だと思うかもしれないコードの変更、または私が行うための標準までですか?私はいくつかのコードレビューサービスを使用してうれしいです:)
0rka

2
関数は、インターフェース引数(インターフェースへのポインターではない)を受け入れる必要があります。呼び出し元は、インターフェイスを実装する構造体へのポインタを渡す必要があります。これは「インターフェイスを関数に渡すという考えを壊す」ものではありません。関数はまだインターフェイスを取り、インターフェイスを実装するコンクリーションを渡します。
エイドリアン

回答:


138

したがって、ここでは2つの概念を混乱させています。構造体へのポインターとインターフェイスへのポインターは同じではありません。インタフェースは、直接構造体のいずれかで保存することができ、または構造体へのポインタ。後者の場合、インターフェースへのポインタではなく、インターフェースを直接使用します。例えば:

type Fooer interface {
    Dummy()
}

type Foo struct{}

func (f Foo) Dummy() {}

func main() {
    var f1 Foo
    var f2 *Foo = &Foo{}

    DoFoo(f1)
    DoFoo(f2)
}

func DoFoo(f Fooer) {
    fmt.Printf("[%T] %+v\n", f, f)
}

出力:

[main.Foo] {}
[*main.Foo] &{}

https://play.golang.org/p/I7H_pv5H3Xl

どちらの場合も、f変数in DoFooは単なるインターフェースであり、インターフェースへのポインターではありません。ただし、を格納するf2場合、インターフェイスFoo構造体へのポインタを保持します。

インターフェイスへのポインタはほとんど役に立ちません。実際、Goランタイムは、いくつかのバージョンで具体的に変更されており、インターフェースポインターの自動逆参照(構造体ポインターの場合のように)が使用されないようになっています。圧倒的多数のケースで、インターフェイスへのポインタは、インターフェイスがどのように機能するかについての誤解を反映しています。

ただし、インターフェースには制限があります。構造体をインターフェースに直接渡す場合、そのタイプのメソッド(つまりfunc (f Foo) Dummy()、ではないfunc (f *Foo) Dummy())のみを使用してインターフェースを満たすことができます。これは、元の構造のコピーをインターフェイスに格納しているため、ポインターメソッドが予期しない影響を与える(つまり、元の構造を変更できない)ためです。したがって、デフォルトの経験則は、やむを得ない理由がない限り、構造体へのポインタをインターフェイス格納することです。

特にコードで、AddFilter関数のシグネチャを次のように変更した場合:

func (fp *FilterMap) AddFilter(f FilterInterface) uuid.UUID

そして、GetFilterByIDシグネチャを次のようにします。

func (fp *FilterMap) GetFilterByID(i uuid.UUID) FilterInterface

コードは期待どおりに動作します。 fieldfilterはタイプ*FieldFilterで、FilterInterfaceインターフェースタイプを完全に満たすため、それAddFilterを受け入れます。

Goでメソッド、型、インターフェースがどのように機能し、統合されるかを理解するための優れたリファレンスをいくつか示します。


「これは、元の構造のコピーをインターフェイスに格納しているため、ポインターメソッドが予期しない影響を与える(つまり、元の構造を変更できない)ためです。」-これは、制限の理由として意味がありません。結局のところ、唯一のコピーがずっとインターフェイスに保存されている可能性があります。
WPWoodJr 2018

そこでのあなたの答えは意味がありません。そこに格納されているものを変更しても、インターフェイスに格納されている具象型が変更されない場所を想定していますが、そうではありません。別のメモリレイアウトで何かを格納している場合は、それは明らかです。私のポインターコメントについて得ていないことは、具象型のポインターレシーバーメソッドは、呼び出されているレシーバーを常に変更できるということです。インターフェイスに格納された値は、参照を取得できないコピーを強制するため、ポインターレシーバーは元のピリオドを変更できません
Kaedys

5
GetFilterByID(i uuid.UUID) *FilterInterface

このエラーが発生するのは、通常、インターフェイスではなくインターフェイスへのポインターを指定しているためです(実際には、インターフェイスを満たす構造体へのポインターになります)。

*インターフェイス{...}の有効な使用方法がありますが、より一般的には、「これは私が書いているコードのポインタであるインターフェイスです」ではなく、「これはポインタです」と考えています

受け入れられた回答は詳細ではありますが、トラブルシューティングに役立たなかったので、そこに捨てるだけです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.