"<Typ> ist Zeiger auf Schnittstelle, nicht Schnittstelle" Verwirrung

102

Liebe Entwicklerkollegen,

Ich habe dieses Problem, das mir etwas komisch vorkommt. Schauen Sie sich diesen Codeausschnitt an:

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
}

Auf einem anderen Paket habe ich den folgenden Code:

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

Die Laufzeit akzeptiert die erwähnte Zeile nicht, weil

"Feldfilter (Typ * coreinterfaces.FieldFilter) kann nicht als Typ * coreinterfaces.FilterInterface im Argument zu fieldint.AddFilter verwendet werden: * coreinterfaces.FilterInterface ist Zeiger auf Schnittstelle, nicht Schnittstelle"

Wenn Sie den Code jedoch ändern in:

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

Alles ist in Ordnung und beim Debuggen der Anwendung scheint es wirklich zu enthalten

Ich bin etwas verwirrt über dieses Thema. Wenn Sie sich andere Blog-Beiträge und Stapelüberlauf-Threads ansehen, in denen genau dasselbe Problem behandelt wird (z. B. Dies oder Dies ), sollte das erste Snippet funktionieren, das diese Ausnahme auslöst, da sowohl Feldfilter als auch Feldzuordnung als Zeiger auf Schnittstellen und nicht als Wert von initialisiert werden Schnittstellen. Ich konnte mich nicht mit dem befassen, was hier tatsächlich passiert und was ich ändern muss, damit ich kein FieldInterface deklariere und die Implementierung für diese Schnittstelle zuweise. Es muss einen eleganten Weg geben, dies zu tun.

0rka
quelle
Beim Wechsel * FilterInterfaceauf FilterInterfacedie Linie _ = filtermap.AddFilter(fieldfilter)jetzt wirft dies: nicht verwenden kann fieldfilter (Typ coreinterfaces.FieldFilter) als Typ coreinterfaces.FilterInterface in Argumente filtermap.AddFilter: coreinterfaces.FieldFilter nicht implementiert coreinterfaces.FilterInterface (Filter - Methode hat Empfänger Zeiger) jedoch , wenn der Wechsel Linie dazu _ = filtermap.AddFilter(&fieldfilter)funktioniert. was geschieht hier? warum ist das so?
0rka
2
Weil die Methoden, die die Schnittstelle implementieren, Zeigerempfänger haben. Wenn Sie einen Wert übergeben, wird die Schnittstelle nicht implementiert. Wenn Sie einen Zeiger übergeben, ist dies der Fall, da die Methoden dann angewendet werden. Im Allgemeinen übergeben Sie beim Umgang mit Schnittstellen einen Zeiger auf eine Struktur an eine Funktion, die eine Schnittstelle erwartet. Sie möchten in keinem Szenario einen Zeiger auf eine Schnittstelle.
Adrian
1
Ich verstehe Ihren Standpunkt, aber durch Ändern des Parameterwerts von * FilterInterfacein eine Struktur, die diese Schnittstelle implementiert, wird die Idee, Schnittstellen an Funktionen zu übergeben, zunichte gemacht. Was ich erreichen wollte, ist nicht an die Struktur gebunden zu sein, die ich übergeben habe, sondern an jede Struktur, die die Schnittstelle implementiert, die ich verwenden möchte . Gibt es Codeänderungen, die Ihrer Meinung nach effizienter sind oder den von mir zu erwartenden Standards entsprechen? Ich werde froh sein, einige
Codeüberprüfungsdienste
2
Ihre Funktion sollte ein Schnittstellenargument akzeptieren (kein Zeiger auf die Schnittstelle). Der Aufrufer sollte einen Zeiger auf eine Struktur übergeben, die die Schnittstelle implementiert. Dies bricht nicht "die Idee, Schnittstellen an Funktionen zu übergeben" - die Funktion benötigt immer noch eine Schnittstelle, Sie übergeben eine Konkretion, die die Schnittstelle implementiert.
Adrian

Antworten:

138

Sie verwechseln hier also zwei Konzepte. Ein Zeiger auf eine Struktur und ein Zeiger auf eine Schnittstelle sind nicht identisch. Eine Schnittstelle kann entweder eine Struktur direkt oder einen Zeiger auf eine Struktur speichern . Im letzteren Fall verwenden Sie immer noch nur die Schnittstelle direkt und keinen Zeiger auf die Schnittstelle. Beispielsweise:

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

Ausgabe:

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

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

In beiden Fällen ist die fVariable in DoFoonur eine Schnittstelle, kein Zeiger auf eine Schnittstelle. Wenn jedoch die Speicherung f2, die Schnittstelle enthält einen Zeiger auf eine FooStruktur.

Zeiger auf Schnittstellen sind fast nie nützlich. Tatsächlich wurde die Go-Laufzeit einige Versionen zurück geändert, um Schnittstellenzeiger nicht mehr automatisch zu dereferenzieren (wie dies bei Strukturzeigern der Fall ist), um deren Verwendung zu verhindern. In den allermeisten Fällen spiegelt ein Zeiger auf eine Schnittstelle ein Missverständnis darüber wider, wie Schnittstellen funktionieren sollen.

Es gibt jedoch eine Einschränkung für Schnittstellen. Wenn Sie eine Struktur direkt an eine Schnittstelle übergeben, können nur Wertmethoden dieses Typs (dh func (f Foo) Dummy()nicht func (f *Foo) Dummy()) verwendet werden, um die Schnittstelle zu erfüllen. Dies liegt daran, dass Sie eine Kopie der ursprünglichen Struktur in der Benutzeroberfläche speichern, sodass Zeigermethoden unerwartete Auswirkungen haben (dh die ursprüngliche Struktur können nicht geändert werden). Die Standard-Faustregel lautet daher , Zeiger auf Strukturen in Schnittstellen zu speichern , es sei denn, es gibt einen zwingenden Grund, dies nicht zu tun.

Speziell mit Ihrem Code, wenn Sie die Signatur der AddFilter-Funktion in Folgendes ändern:

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

Und die GetFilterByID-Signatur für:

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

Ihr Code funktioniert wie erwartet. fieldfilterist vom Typ *FieldFilter, der den FilterInterfaceSchnittstellentyp vollständig ausfüllt und ihn somit AddFilterakzeptiert.

Im Folgenden finden Sie einige gute Referenzen zum Verständnis der Funktionsweise und Integration von Methoden, Typen und Schnittstellen in Go:

Kaedys
quelle
"Dies liegt daran, dass Sie eine Kopie der ursprünglichen Struktur in der Benutzeroberfläche speichern, sodass Zeigermethoden unerwartete Auswirkungen haben (dh die ursprüngliche Struktur können nicht geändert werden)" - dies ist als Grund für die Einschränkung nicht sinnvoll. Schließlich wurde möglicherweise nur die einzige Kopie in der Benutzeroberfläche gespeichert.
WPWoodJr
Ihre Antwort dort macht keinen Sinn. Sie gehen davon aus, dass sich der Speicherort des in der Schnittstelle gespeicherten konkreten Typs nicht ändert, wenn Sie den dort gespeicherten Typ ändern, was nicht der Fall ist. Dies sollte offensichtlich sein, wenn Sie etwas mit einem anderen Speicherlayout speichern. Was Sie über meinen Zeigerkommentar nicht verstehen, ist, dass eine Zeigerempfängermethode für einen konkreten Typ immer den Empfänger ändern kann, auf dem sie aufgerufen wird. Ein in einer Schnittstelle gespeicherter Wert erzwingt eine Kopie, auf die Sie dann keinen Verweis erhalten können, sodass Zeigerempfänger den ursprünglichen Zeitraum nicht ändern können .
Kaedys
5
GetFilterByID(i uuid.UUID) *FilterInterface

Wenn ich diesen Fehler erhalte, liegt dies normalerweise daran, dass ich anstelle einer Schnittstelle einen Zeiger auf eine Schnittstelle gebe (dies ist tatsächlich ein Zeiger auf meine Struktur, die die Schnittstelle erfüllt).

Es gibt eine gültige Verwendung für * interface {...}, aber häufiger denke ich nur, dass dies ein Zeiger ist, anstatt dass dies eine Schnittstelle ist, die zufällig ein Zeiger in dem Code ist, den ich schreibe.

Ich habe es einfach rausgeworfen, weil die akzeptierte Antwort, obwohl detailliert, mir nicht bei der Fehlerbehebung geholfen hat.

Daniel Farrell
quelle