X non implementa Y (... il metodo ha un ricevitore puntatore) [chiuso]


201

Ci sono già diverse domande e risposte su questo " X non implementa Y (... il metodo ha un ricevitore puntatore) ", ma per me sembrano parlare di cose diverse e non si applicano al mio caso specifico.

Quindi, invece di rendere la domanda molto specifica, la sto rendendo ampia e astratta - Sembra che ci siano diversi casi che possono far accadere questo errore, qualcuno può riassumerlo per favore?

Cioè, come evitare il problema, e se si verifica, quali sono le possibilità? Grazie.

Risposte:


365

Questo errore di compilazione si verifica quando si tenta di assegnare o passare (o convertire) un tipo concreto a un tipo di interfaccia; e il tipo stesso non implementa l'interfaccia, solo un puntatore al tipo .

Vediamo un esempio:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

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

Il Stringertipo di interfaccia ha un solo metodo: String(). Qualsiasi valore memorizzato in un valore di interfaccia Stringerdeve avere questo metodo. Abbiamo anche creato un MyTypee abbiamo creato un metodo MyType.String()con il ricevitore puntatore . Ciò significa che il String()metodo è nel set di metodi del *MyTypetipo, ma non in quello di MyType.

Quando proviamo ad assegnare un valore MyTypea una variabile di tipo Stringer, otteniamo l'errore in questione:

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)

Ma è tutto ok se proviamo ad assegnare un valore di tipo *MyTypea Stringer:

s = &m
fmt.Println(s)

E otteniamo il risultato atteso (provalo sul Go Playground ):

something

Quindi i requisiti per ottenere questo errore di compilazione:

  • Un valore di tipo concreto non puntatore assegnato (o passato o convertito)
  • Un tipo di interfaccia assegnato (o passato o convertito in)
  • Il tipo concreto ha il metodo richiesto dell'interfaccia, ma con un ricevitore puntatore

Possibilità di risolvere il problema:

  • È necessario utilizzare un puntatore al valore, il cui set di metodi includerà il metodo con il ricevitore puntatore
  • In alternativa, il tipo di ricevitore deve essere modificato in non puntatore , quindi il set di metodi del tipo concreto non puntatore conterrà anche il metodo (e quindi soddisfare l'interfaccia). Ciò può essere o non essere fattibile, come se il metodo dovesse modificare il valore, un ricevitore senza puntatore non è un'opzione.

Strutture e incorporamento

Quando si utilizzano le strutture e l'incorporamento , spesso non è "tu" l'implementazione di un'interfaccia (fornire un'implementazione del metodo), ma un tipo che incorpori nel tuo struct. Come in questo esempio:

type MyType2 struct {
    MyType
}

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

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

Ancora una volta, errore di compilazione, perché il set di metodi di MyType2non contiene il String()metodo del incorporato MyType, solo il set di metodi di *MyType2, quindi il seguente funziona (provalo sul Play Goground ):

var s Stringer
s = &m2

Possiamo anche farlo funzionare, se incorporiamo *MyTypee utilizziamo solo un non puntatore MyType2 (provalo nel Play Goground ):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = m2

Inoltre, qualunque cosa incorporiamo (o MyTypeo *MyType), se utilizziamo un puntatore *MyType2, funzionerà sempre (provalo nel Play Goground ):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = &m2

Sezione pertinente delle specifiche (dalla sezione Tipi di strutture ):

Dato un tipo di struttura Se un tipo denominato T, i metodi promossi sono inclusi nel set di metodi della struttura come segue:

  • Se Scontiene un campo anonimo T, i set di metodi di Sed *Sentrambi includono metodi promossi con ricevitore T. Il set di *Smetodi include anche metodi promossi con ricevitore *T.
  • Se Scontiene un campo anonimo *T, il metodo set di Se *Sentrambi includono metodi con ricevitore promosso To *T.

Quindi, in altre parole: se incorporiamo un tipo non puntatore, l'insieme di metodi dell'embedder non puntatore ottiene solo i metodi con ricevitori non puntatore (dal tipo incorporato).

Se incorporiamo un tipo di puntatore, il set di metodi dell'embedder senza puntatore ottiene metodi con entrambi i ricevitori puntatore e non puntatore (dal tipo incorporato).

Se utilizziamo un valore di puntatore per l'embedder, indipendentemente dal fatto che il tipo incorporato sia puntatore o meno, l'insieme di metodi del puntatore all'embedder ottiene sempre metodi con entrambi i ricevitori puntatore e non puntatore (dal tipo incorporato).

Nota:

C'è un caso molto simile, vale a dire quando si dispone di un valore di interfaccia che racchiude un valore di MyTypee si tenta di digitare affermare un altro valore di interfaccia da esso Stringer,. In questo caso l'asserzione non sarà valida per i motivi sopra descritti, ma si ottiene un errore di runtime leggermente diverso:

m := MyType{value: "something"}

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

Panico di runtime (provalo sul Go Playground ):

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

Tentando di convertire anziché digitare assert, otteniamo l'errore di compilazione di cui stiamo parlando:

m := MyType{value: "something"}

fmt.Println(Stringer(m))

Grazie per la risposta estremamente completa. Ci scusiamo per la risposta in ritardo perché stranamente non ho ricevuto la notifica SO. Un caso che ho cercato, la risposta è stata che le "funzioni membro" dovrebbero essere di tutti i tipi di puntatore, ad esempio " func (m *MyType)" o nessuna . È così? Posso mescolare diversi tipi di "funzioni membro", ad es. func (m *MyType)& func (m MyType)?
xpt,

3
@xpt È possibile combinare ricevitori puntatore e non puntatore, non è un requisito per fare lo stesso. È strano se hai 19 metodi con ricevitore puntatore e ne crei uno con ricevitore non puntatore. Inoltre, è più difficile tenere traccia di quali metodi fanno parte di quali tipi di metodi vengono impostati se si inizia a mescolarli. Maggiori dettagli in questa risposta: Ricevitore di valore vs. Ricevitore puntatore a Golang?
Icza,

Come risolvi effettivamente il problema menzionato alla fine in "Nota:" con un'interfaccia {} che racchiude un valore di MyType, se non puoi cambiare MyTypeper usare i metodi di valore. Ho provato qualcosa del genere (&i).(*Stringer)ma non funziona. È anche possibile?
Joel Edström,

1
@ JoelEdström Sì, è possibile, ma ha poco senso. Ad esempio, è possibile digitare il valore del tipo non puntatore e memorizzarlo in una variabile, ad esempio x := i.(MyType), e quindi è possibile chiamare metodi con ricevitore puntatore su di esso, ad esempio i.String(), che è una scorciatoia per la (&i).String()quale ha successo perché le variabili sono indirizzabili. Ma il metodo del puntatore che modifica il valore (il valore puntato) non si rifletterà nel valore racchiuso nel valore dell'interfaccia, ecco perché ha poco senso.
Icza,

1
@DeepNightTwo I metodi di *Tnon sono inclusi nel set di metodi Sperché Spotrebbero non essere indirizzabili (ad es. Valore di ritorno della funzione o risultato dell'indicizzazione della mappa), e anche perché spesso è presente / ricevuta solo una copia e se è consentito prendere il suo indirizzo, il metodo con pointer pointer è possibile modificare solo la copia (confusione come si suppone che l'originale sia stato modificato). Vedere questa risposta per un esempio: utilizzo di SetString di riflessione .
Icza,

33

Per farla breve, supponiamo di avere questo codice e di avere un'interfaccia Loader e un WebLoader che implementa questa interfaccia.

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

Quindi questo codice ti darà questo errore di compilazione

./main.go:20:13: impossibile utilizzare webLoader (tipo WebLoader) come caricatore di tipo nell'argomento loadContent: WebLoader non implementa Loader (il metodo Load ha un ricevitore puntatore)

Quindi, ciò che devi solo fare è cambiare webLoader := WebLoader{}a quanto segue:

webLoader := &WebLoader{} 

Quindi perché risolverà perché si definisce questa funzione func (w *WebLoader) Loadper accettare un ricevitore puntatore. Per ulteriori spiegazioni, leggi le risposte di @icza e @karora


6
Di gran lunga questo è stato il commento più semplice da capire. E risolto direttamente il problema che stavo affrontando ..
Maxs728

@ Maxs728 D'accordo, abbastanza raro nelle risposte ai molti problemi di Go.
milosmns,

6

Un altro caso in cui ho visto accadere questo genere di cose è se voglio creare un'interfaccia in cui alcuni metodi modificheranno un valore interno e altri no.

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

Qualcosa che quindi implementa questa interfaccia potrebbe essere come:

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
}

Quindi il tipo di implementazione avrà probabilmente alcuni metodi che sono ricevitori puntatore e altri che non lo sono e dato che ho una varietà di queste varie cose che sono GetterSetter mi piacerebbe controllare nei miei test che tutti stiano facendo il previsto.

Se dovessi fare qualcosa del genere:

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

Allora non sarà possibile ottenere il già citato "X non implementa Y (metodo Z ha ricevitore puntatore)" Errore (in quanto si tratta di un errore di compilazione), ma io voglio avere una brutta giornata inseguire esattamente il motivo per il mio test sta venendo a mancare .. .

Devo invece assicurarmi di eseguire il controllo del tipo utilizzando un puntatore, ad esempio:

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

O:

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

Allora tutto è contento dei test!

Ma aspetta! Nel mio codice, forse ho metodi che accettano un GetterSetter da qualche parte:

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

Se chiamo questi metodi all'interno di un altro metodo di tipo, questo genererà l'errore:

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

Entrambe le seguenti chiamate funzioneranno:

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

func (m MyTypeA) OtherThing(x int) {
    SomeStuff(&m, x)
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.