"<tipo> è un puntatore all'interfaccia, non una confusione di interfaccia"


104

Cari colleghi sviluppatori,

Ho questo problema che mi sembra un po 'strano. Dai un'occhiata a questo frammento di codice:

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
}

Su qualche altro pacchetto, ho il seguente codice:

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

Il run-time non accetterà la riga menzionata perché

"non è possibile utilizzare fieldfilter (tipo * coreinterfaces.FieldFilter) come tipo * coreinterfaces.FilterInterface nell'argomento di fieldint.AddFilter: * coreinterfaces.FilterInterface è un puntatore all'interfaccia, non all'interfaccia"

Tuttavia, quando si modifica il codice 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)
}

Va tutto bene e durante il debug dell'applicazione sembra davvero includere

Sono un po 'confuso su questo argomento. Quando si esaminano altri post del blog e thread di overflow dello stack che discutono di questo stesso identico problema (ad esempio - Questo o Questo ) il primo frammento che solleva questa eccezione dovrebbe funzionare, perché sia ​​fieldfilter che fieldmap sono inizializzati come puntatori alle interfacce, piuttosto che valore di interfacce. Non sono stato in grado di capire cosa succede effettivamente qui che devo cambiare per non dichiarare un FieldInterface e assegnare l'implementazione per quell'interfaccia. Ci deve essere un modo elegante per farlo.


Quando si * FilterInterfacepassa a FilterInterfaceLa riga _ = filtermap.AddFilter(fieldfilter)ora solleva questo: non è possibile utilizzare fieldfilter (tipo coreinterfaces.FieldFilter) come tipo coreinterfaces.FilterInterface nell'argomento di filtermap.AddFilter: coreinterfaces.FieldFilter non implementa coreinterfaces.FilterInterface (il metodo Filter ha il ricevitore del puntatore) Tuttavia quando si cambia il linea per _ = filtermap.AddFilter(&fieldfilter)funzionare. Che succede qui? perché?
0rka

2
Perché i metodi che implementano l'interfaccia hanno ricevitori di puntatori. Passando un valore, non implementa l'interfaccia; passando un puntatore, lo fa, perché i metodi vengono applicati. In generale, quando si ha a che fare con le interfacce, si passa un puntatore a una struttura a una funzione che si aspetta un'interfaccia. Non vuoi quasi mai un puntatore a un'interfaccia in nessuno scenario.
Adrian

1
Capisco il tuo punto, ma cambiando il valore del parametro da * FilterInterfacea una struttura che implementa questa interfaccia, questo rompe l'idea di passare le interfacce alle funzioni. Quello che volevo ottenere non era essere vincolato a quale struttura stavo passando, ma piuttosto a qualsiasi struttura che implementa l'interfaccia che mi interessa usare. Eventuali modifiche al codice che potresti ritenere più efficienti o conformi agli standard per me? Sarò felice di utilizzare alcuni servizi di revisione del codice :)
0rka

2
La funzione dovrebbe accettare un argomento dell'interfaccia (non un puntatore all'interfaccia). Il chiamante dovrebbe passare un puntatore a una struttura che implementa l'interfaccia. Questo non "rompe l'idea di passare interfacce alle funzioni" - la funzione accetta ancora un'interfaccia, stai passando a una concrezione che implementa l'interfaccia.
Adrian

Risposte:


140

Quindi stai confondendo due concetti qui. Un puntatore a una struttura e un puntatore a un'interfaccia non sono la stessa cosa. Un'interfaccia può memorizzare direttamente una struttura o un puntatore a una struttura. In quest'ultimo caso, usi ancora direttamente l'interfaccia, non un puntatore all'interfaccia. Per esempio:

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

Produzione:

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

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

In entrambi i casi, la fvariabile in DoFooè solo un'interfaccia, non un puntatore a un'interfaccia. Tuttavia, durante l'archiviazione f2, l'interfaccia contiene un puntatore a un fileFoo struttura.

I puntatori alle interfacce non sono quasi mai utili. In effetti, il runtime di Go è stato specificamente modificato in alcune versioni per non dereferenziare più automaticamente i puntatori all'interfaccia (come fa per i puntatori alla struttura), per scoraggiarne l'uso. Nella stragrande maggioranza dei casi, un puntatore a un'interfaccia riflette un malinteso su come dovrebbero funzionare le interfacce.

Tuttavia, esiste una limitazione sulle interfacce. Se si passa una struttura direttamente in un'interfaccia, solo i metodi di valore di quel tipo (cioè func (f Foo) Dummy(), non func (f *Foo) Dummy()) possono essere utilizzati per soddisfare l'interfaccia. Questo perché stai memorizzando una copia della struttura originale nell'interfaccia, quindi i metodi del puntatore avrebbero effetti inaspettati (cioè incapaci di alterare la struttura originale). Pertanto, la regola pratica predefinita è memorizzare i puntatori a strutture nelle interfacce , a meno che non ci sia un motivo valido per non farlo.

In particolare con il codice, se si modifica la firma della funzione AddFilter in:

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

E la firma GetFilterByID per:

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

Il tuo codice funzionerà come previsto. fieldfilterè di tipo *FieldFilter, che riempie il FilterInterfacetipo di interfaccia e quindi AddFilterlo accetterà.

Ecco un paio di buoni riferimenti per capire come i metodi, i tipi e le interfacce funzionano e si integrano tra loro in Go:


"Questo perché stai memorizzando una copia della struttura originale nell'interfaccia, quindi i metodi del puntatore avrebbero effetti inaspettati (cioè incapaci di alterare la struttura originale)" - questo non ha senso come motivo della limitazione. Dopotutto, l'unica copia potrebbe essere stata memorizzata nell'interfaccia per tutto il tempo.
WPWoodJr

La tua risposta non ha senso. Stai assumendo che la posizione in cui il tipo concreto memorizzato nell'interfaccia non cambia quando cambi ciò che è memorizzato lì, il che non è il caso, e ciò dovrebbe essere ovvio se stai memorizzando qualcosa con un layout di memoria diverso. Quello che non ottieni dal mio commento sul puntatore è che un metodo ricevitore del puntatore su un tipo concreto può farlo sempre modificare il ricevitore su cui viene chiamato. Un valore memorizzato in un'interfaccia forza una copia a cui non è possibile ottenere un riferimento, quindi i destinatari del puntatore non possono modificare l'originale, punto.
Kaedys

5
GetFilterByID(i uuid.UUID) *FilterInterface

Quando ricevo questo errore, di solito è perché sto specificando un puntatore a un'interfaccia invece di un'interfaccia (che in realtà sarà un puntatore alla mia struttura che soddisfa l'interfaccia).

C'è un uso valido per * interfaccia {...} ma più comunemente penso solo "questo è un puntatore" invece di "questa è un'interfaccia che sembra essere un puntatore nel codice che sto scrivendo"

Lanciarlo lì perché la risposta accettata, sebbene dettagliata, non mi ha aiutato a risolvere i problemi.

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.