"<type> est un pointeur vers l'interface, pas une interface" confusion

108

Chers collègues développeurs,

J'ai ce problème qui me semble un peu étrange. Jetez un œil à cet extrait de code:

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
}

Sur un autre package, j'ai le code suivant:

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

Le run-time n'acceptera pas la ligne mentionnée car

"ne peut pas utiliser fieldfilter (type * coreinterfaces.FieldFilter) comme type * coreinterfaces.FilterInterface dans l'argument de fieldint.AddFilter: * coreinterfaces.FilterInterface est un pointeur vers l'interface, pas l'interface"

Cependant, lorsque vous modifiez le code en:

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

Tout va bien et lors du débogage de l'application, il semble vraiment inclure

Je suis un peu confus sur ce sujet. Lorsque vous consultez d'autres articles de blog et les threads de débordement de pile discutant exactement du même problème (par exemple - Ceci ou Ceci ), le premier extrait de code qui lève cette exception devrait fonctionner, car fieldfilter et fieldmap sont initialisés en tant que pointeurs vers des interfaces, plutôt que comme valeur de les interfaces. Je n'ai pas été en mesure de comprendre ce qui se passe réellement ici et que je dois changer pour ne pas déclarer une FieldInterface et affecter l'implémentation de cette interface. Il doit y avoir une manière élégante de faire cela.

0rka
la source
Lors du passage * FilterInterfaceà FilterInterfaceLa ligne _ = filtermap.AddFilter(fieldfilter)lève maintenant ceci: impossible d'utiliser fieldfilter (type coreinterfaces.FieldFilter) comme type coreinterfaces.FilterInterface en argument en filtermap.AddFilter: coreinterfaces.FieldFilter n'implémente pas coreinterfaces.FilterInterface (la méthode Filter a un récepteur de pointeur) Cependant lors du changement de ligne à _ = filtermap.AddFilter(&fieldfilter)cela fonctionne. Que se passe t-il ici? pourquoi donc?
0rka
2
Parce que les méthodes qui implémentent l'interface ont des récepteurs de pointeur. En passant une valeur, il n'implémente pas l'interface; en passant un pointeur, c'est le cas, car les méthodes s'appliquent alors. De manière générale, lorsque vous traitez avec des interfaces, vous passez un pointeur vers une structure vers une fonction qui attend une interface. Vous ne voulez presque jamais d'un pointeur vers une interface dans aucun scénario.
Adrian
1
Je comprends votre point de vue, mais en changeant la valeur du paramètre de * FilterInterfaceen une structure qui implémente cette interface, cela rompt l'idée de passer des interfaces aux fonctions. Ce que je voulais accomplir, ce n'est pas d'être lié à la structure que je passais, mais plutôt à toute structure qui implémente l'interface que je suis intéressé à utiliser. Y a-t-il des changements de code que vous pensez être plus efficaces ou conformes aux normes que je dois faire? Je serai heureux d'utiliser certains services de révision de code :)
0rka
2
Votre fonction doit accepter un argument d'interface (pas de pointeur vers l'interface). L'appelant doit passer un pointeur vers une structure qui implémente l'interface. Cela ne "casse pas l'idée de passer des interfaces aux fonctions" - la fonction prend toujours une interface, vous passez dans une concrétion qui implémente l'interface.
Adrian

Réponses:

145

Vous confondez donc deux concepts ici. Un pointeur vers une structure et un pointeur vers une interface ne sont pas les mêmes. Une interface peut stocker directement une structure ou un pointeur vers une structure. Dans ce dernier cas, vous utilisez toujours simplement l'interface directement, pas un pointeur vers l'interface. Par exemple:

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

Production:

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

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

Dans les deux cas, la fvariable in DoFooest simplement une interface, pas un pointeur vers une interface. Cependant, lors du stockage f2, l'interface contient un pointeur vers une Foostructure.

Les pointeurs vers les interfaces ne sont presque jamais utiles. En fait, le runtime Go a été spécifiquement modifié quelques versions pour ne plus déréférencer automatiquement les pointeurs d'interface (comme c'est le cas pour les pointeurs de structure), afin de décourager leur utilisation. Dans la très grande majorité des cas, un pointeur vers une interface reflète un malentendu sur la façon dont les interfaces sont censées fonctionner.

Cependant, il existe une limitation sur les interfaces. Si vous passez une structure directement dans une interface, seules les méthodes de valeur de ce type (c'est-à-dire func (f Foo) Dummy()non func (f *Foo) Dummy()) peuvent être utilisées pour remplir l'interface. C'est parce que vous stockez une copie de la structure originale dans l'interface, donc les méthodes de pointeur auraient des effets inattendus (c'est-à-dire incapables de modifier la structure originale). Ainsi, la règle empirique par défaut est de stocker des pointeurs vers des structures dans des interfaces , à moins qu'il n'y ait une raison impérieuse de ne pas le faire.

Plus précisément avec votre code, si vous modifiez la signature de la fonction AddFilter en:

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

Et la signature GetFilterByID pour:

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

Votre code fonctionnera comme prévu. fieldfilterest de type *FieldFilter, qui remplit le FilterInterfacetype d'interface, et donc AddFilterl'acceptera.

Voici quelques bonnes références pour comprendre comment les méthodes, les types et les interfaces fonctionnent et s'intègrent les uns aux autres dans Go:

Kaedys
la source
"C'est parce que vous stockez une copie de la structure originale dans l'interface, donc les méthodes de pointeur auraient des effets inattendus (c'est-à-dire incapables de modifier la structure originale)" - cela n'a pas de sens comme raison de la limitation. Après tout, la seule copie peut avoir été stockée dans l'interface depuis le début.
WPWoodJr
Votre réponse n'a aucun sens. Vous supposez que l'emplacement où le type concret stocké dans l'interface ne change pas lorsque vous modifiez ce qui y est stocké, ce qui n'est pas le cas, et cela devrait être évident si vous stockez quelque chose avec une disposition de mémoire différente. Ce que vous n'obtenez pas dans mon commentaire de pointeur, c'est qu'une méthode de récepteur de pointeur sur un type concret peut toujours modifier le récepteur sur lequel elle est appelée. Une valeur stockée dans une interface force une copie à laquelle vous ne pouvez pas ensuite obtenir une référence, de sorte que les récepteurs de pointeurs ne peuvent pas modifier le point d'origine.
Kaedys
5
GetFilterByID(i uuid.UUID) *FilterInterface

Lorsque j'obtiens cette erreur, c'est généralement parce que je spécifie un pointeur vers une interface au lieu d'une interface (qui sera en fait un pointeur vers ma structure qui remplit l'interface).

Il y a une utilisation valide pour * interface {...} mais le plus souvent je pense juste "ceci est un pointeur" au lieu de "ceci est une interface qui se trouve être un pointeur dans le code que j'écris"

Je l'ai simplement lancé parce que la réponse acceptée, bien que détaillée, ne m'a pas aidé à résoudre le problème.

Daniel Farrell
la source