X implementiert Y nicht (… Methode hat einen Zeigerempfänger) [geschlossen]

201

Es gibt bereits mehrere Fragen und Antworten zu diesem Thema " X implementiert kein Y (... Methode hat einen Zeigerempfänger) ", aber für mich scheinen sie über verschiedene Dinge zu sprechen und gelten nicht für meinen speziellen Fall.

Anstatt die Frage sehr spezifisch zu machen, mache ich sie breit und abstrakt. Es scheint, dass es verschiedene Fälle gibt, die diesen Fehler verursachen können. Kann jemand sie bitte zusammenfassen?

Dh wie kann das Problem vermieden werden, und wenn es auftritt, welche Möglichkeiten gibt es? Vielen Dank.

xpt
quelle

Antworten:

364

Dieser Fehler bei der Kompilierung tritt auf, wenn Sie versuchen, einen konkreten Typ einem Schnittstellentyp zuzuweisen oder zu übergeben (oder zu konvertieren) . und der Typ selbst implementiert nicht die Schnittstelle, sondern nur einen Zeiger auf den Typ .

Sehen wir uns ein Beispiel an:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

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

Der StringerSchnittstellentyp hat nur eine Methode : String(). Jeder Wert, der in einem Schnittstellenwert gespeichert ist, Stringermuss über diese Methode verfügen. Wir haben auch eine MyTypeund eine Methode MyType.String()mit Zeigerempfänger erstellt . Dies bedeutet, dass sich die String()Methode im Methodensatz des *MyTypeTyps befindet, nicht jedoch im MyType.

Wenn wir versuchen, MyTypeeiner Variablen vom Typ einen Wert zuzuweisen Stringer, erhalten wir den fraglichen Fehler:

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)

Aber alles ist in Ordnung , wenn wir versuchen , einen Wert vom Typ zuweisen *MyTypezu Stringer:

s = &m
fmt.Println(s)

Und wir bekommen das erwartete Ergebnis (probieren Sie es auf dem Go Playground aus ):

something

Die Voraussetzungen, um diesen Fehler bei der Kompilierung zu erhalten:

  • Ein Wert des Nicht-Zeiger -Betontyps wird zugewiesen (oder übergeben oder konvertiert).
  • Ein Schnittstellentyp, der zugewiesen (oder übergeben oder konvertiert) wird.
  • Der konkrete Typ hat die erforderliche Methode der Schnittstelle, jedoch mit einem Zeigerempfänger

Möglichkeiten zur Behebung des Problems:

  • Es muss ein Zeiger auf den Wert verwendet werden, dessen Methodensatz die Methode mit dem Zeigerempfänger enthält
  • Oder der Empfängertyp muss in Nicht-Zeiger geändert werden , sodass der Methodensatz des Nicht-Zeiger-Betontyps auch die Methode enthält (und somit die Schnittstelle erfüllt). Dies kann sinnvoll sein oder auch nicht, als ob die Methode den Wert ändern muss, ein Nicht-Zeiger-Empfänger ist keine Option.

Strukturen und Einbettung

Bei der Verwendung von Strukturen und beim Einbetten implementieren häufig nicht "Sie" eine Schnittstelle (stellen eine Methodenimplementierung bereit), sondern ein Typ, den Sie in Ihre einbetten struct. Wie in diesem Beispiel:

type MyType2 struct {
    MyType
}

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

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

Wiederum ein Fehler beim Kompilieren, da der Methodensatz von MyType2nicht die String()Methode des eingebetteten MyType, sondern nur den Methodensatz von enthält *MyType2, sodass Folgendes funktioniert (versuchen Sie es auf dem Go Playground ):

var s Stringer
s = &m2

Wir können es auch zum Laufen bringen, wenn wir *MyTypenur einen Nicht-Zeiger einbetten und verwenden MyType2(versuchen Sie es auf dem Go-Spielplatz ):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = m2

Was auch immer wir einbetten (entweder MyTypeoder *MyType), wenn wir einen Zeiger verwenden *MyType2, wird es immer funktionieren (versuchen Sie es auf dem Go Playground ):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = &m2

Relevanter Abschnitt aus der Spezifikation (aus Abschnitt Strukturtypen ):

Bei einem gegebenen Strukturtyp Sund einem benannten Typ Tsind heraufgestufte Methoden wie folgt im Methodensatz der Struktur enthalten:

  • Wenn es Sein anonymes Feld enthält T, enthalten die Methodensätze von Sund *Sbeide heraufgestufte Methoden mit Empfänger T. Der Methodensatz von *Senthält auch gesponserte Methoden mit Empfänger *T.
  • Wenn es Sein anonymes Feld enthält *T, enthalten die Methodensätze von Sund *Sbeide heraufgestufte Methoden mit Empfänger Toder *T.

Mit anderen Worten: Wenn wir einen Nicht-Zeiger-Typ einbetten, erhält der Methodensatz des Nicht-Zeiger-Einbetters nur die Methoden mit Nicht-Zeiger-Empfängern (vom eingebetteten Typ).

Wenn wir einen Zeigertyp einbetten, erhält der Methodensatz des Nicht-Zeiger-Einbetters Methoden mit Zeiger- und Nicht-Zeiger-Empfängern (vom eingebetteten Typ).

Wenn wir einen Zeigerwert für den Einbettungswert verwenden, unabhängig davon, ob der eingebettete Typ ein Zeiger ist oder nicht, erhält der Methodensatz des Zeigers auf den Einbettungsmodus immer Methoden sowohl mit dem Zeiger- als auch mit dem Nichtzeigerempfänger (vom eingebetteten Typ).

Hinweis:

Es gibt einen sehr ähnlichen Fall, nämlich dann , wenn Sie eine Schnittstelle Wert haben, der einen Wert von Wraps MyType, und Sie versuchen, assert zu geben eine andere Schnittstelle Wert von ihm, Stringer. In diesem Fall gilt die Behauptung aus den oben beschriebenen Gründen nicht, aber wir erhalten einen etwas anderen Laufzeitfehler:

m := MyType{value: "something"}

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

Laufzeit-Panik (probieren Sie es auf dem Go-Spielplatz ):

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

Beim Versuch, anstelle des Typs assert zu konvertieren, wird der Fehler angezeigt, von dem wir sprechen:

m := MyType{value: "something"}

fmt.Println(Stringer(m))
icza
quelle
Vielen Dank für die äußerst umfassende Antwort. Es tut mir leid, dass ich zu spät geantwortet habe, da ich seltsamerweise keine SO-Benachrichtigung erhalten habe. In einem Fall, den ich gesucht habe, war die Antwort, dass die "Elementfunktionen" entweder alle Zeigertypen sein sollten, z. B. " func (m *MyType)", oder keine . Ist es so? Kann ich verschiedene Arten von "Elementfunktionen" mischen, z. B. func (m *MyType)& func (m MyType)?
xpt
3
@xpt Sie können Zeiger- und Nichtzeigerempfänger mischen, es ist nicht erforderlich, alle gleich zu machen. Es ist nur seltsam, wenn Sie 19 Methoden mit Zeigerempfänger haben und eine mit Nicht-Zeigerempfänger erstellen. Es macht es auch schwieriger zu verfolgen, welche Methoden Teil der Methodensätze welcher Typen sind, wenn Sie mit dem Mischen beginnen. Weitere Details in dieser Antwort: Wertempfänger vs. Zeigerempfänger in Golang?
icza
Wie lösen Sie das am Ende in "Hinweis:" erwähnte Problem tatsächlich mit einer Schnittstelle {}, die einen Wert von umschließt MyType, wenn Sie die MyTypeVerwendung von Wertmethoden nicht ändern können? Ich habe so etwas versucht, (&i).(*Stringer)aber es funktioniert nicht. Ist es überhaupt möglich?
Joel Edström
1
@ JoelEdström Ja, das ist möglich, aber es macht wenig Sinn. Beispielsweise können Sie den Wert des Nicht-Zeigertyps typenbestätigen und in einer Variablen speichern, z. B. x := i.(MyType)können Sie dann Methoden mit Zeigerempfänger aufrufen, z. B. i.String()eine Abkürzung, (&i).String()die erfolgreich ist, weil Variablen adressierbar sind. Die Zeigermethode, mit der der Wert (der spitze Wert) geändert wird, spiegelt sich jedoch nicht in dem Wert wider, der in den Schnittstellenwert eingeschlossen ist. Deshalb ist dies wenig sinnvoll.
icza
1
@DeepNightTwo Methoden von *Tsind nicht im Methodensatz von enthalten, Sweil sie Smöglicherweise nicht adressierbar sind (z. B. Funktionsrückgabewert oder Ergebnis der Kartenindizierung), und auch, weil häufig nur eine Kopie vorhanden / empfangen ist und die Verwendung der Adresse zulässig ist mit Zeigerempfänger konnte nur die Kopie geändert werden (Verwirrung, da Sie annehmen würden, dass das Original geändert wurde). In dieser Antwort finden Sie ein Beispiel: Verwenden von Reflection SetString .
icza
33

Nehmen wir an, Sie haben diesen Code und eine Loader-Schnittstelle und einen WebLoader, der diese Schnittstelle implementiert.

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

Dieser Code gibt Ihnen also diesen Fehler bei der Kompilierung

./main.go:20:13: WebLoader (Typ WebLoader) kann nicht als Typ Loader im Argument für loadContent verwendet werden: WebLoader implementiert Loader nicht (Load-Methode hat Zeigerempfänger)

Sie müssen also nur webLoader := WebLoader{}Folgendes ändern :

webLoader := &WebLoader{} 

Warum wird das Problem behoben, weil Sie diese Funktion so definieren func (w *WebLoader) Load, dass sie einen Zeigerempfänger akzeptiert? Für weitere Erklärungen lesen Sie bitte die Antworten von @icza und @karora

Saman Shafigh
quelle
6
Dies war bei weitem der am einfachsten zu verstehende Kommentar. Und löste direkt das Problem, mit dem ich konfrontiert war ..
Maxs728
@ Maxs728 Einverstanden, ziemlich ungewöhnlich bei Antworten auf die vielen vielen Go-Probleme.
Milosmns
6

Ein anderer Fall, in dem ich so etwas gesehen habe, ist, wenn ich eine Schnittstelle erstellen möchte, in der einige Methoden einen internen Wert ändern und andere nicht.

type GetterSetter interface {
   GetVal() int
   SetVal(x int) int
}

Etwas, das diese Schnittstelle dann implementiert, könnte sein:

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
}

Der Implementierungstyp wird wahrscheinlich einige Methoden haben, die Zeigerempfänger sind, und einige, die es nicht sind, und da ich eine ganze Reihe dieser verschiedenen Dinge habe, die GetterSetter sind, möchte ich in meinen Tests überprüfen, ob sie alle die erwarteten Ergebnisse erzielen.

Wenn ich so etwas machen würde:

myTypeInstance := MyType{ 7 }
... maybe some code doing other stuff ...
var f interface{} = myTypeInstance
_, ok := f.(GetterSetter)
if !ok {
    t.Fail()
}

Dann erhalte ich nicht den oben genannten Fehler "X implementiert Y nicht (Z-Methode hat Zeigerempfänger)" (da es sich um einen Fehler zur Kompilierungszeit handelt), aber ich werde einen schlechten Tag haben, um genau zu verfolgen, warum mein Test fehlschlägt. .

Stattdessen muss ich sicherstellen, dass ich die Typprüfung mit einem Zeiger durchführe, wie z.

var f interface{} = new(&MyTypeA)
 ...

Oder:

myTypeInstance := MyType{ 7 }
var f interface{} = &myTypeInstance
...

Dann ist alles mit den Tests zufrieden!

Aber warte! In meinem Code habe ich vielleicht Methoden, die irgendwo einen GetterSetter akzeptieren:

func SomeStuff(g GetterSetter, x int) int {
    if x > 10 {
        return g.GetVal() + 1
    }
    return g.GetVal()
}

Wenn ich diese Methoden aus einer anderen Typmethode heraus aufrufe, wird der Fehler generiert:

func (m MyTypeA) OtherThing(x int) {
    SomeStuff(m, x)
}

Jeder der folgenden Aufrufe funktioniert:

func (m *MyTypeA) OtherThing(x int) {
    SomeStuff(m, x)
}

func (m MyTypeA) OtherThing(x int) {
    SomeStuff(&m, x)
}
Karora
quelle