web-dev-qa-db-fra.com

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

Chers collègues développeurs,

J'ai ce problème qui me semble un peu bizarre. Jetez un coup d'œ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 paquet, 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) en tant que type * coreinterfaces.FilterInterface dans un argument pour fieldint.AddFilter: Coreinterfaces.FilterInterface est un pointeur sur une interface, pas une 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 examinez d'autres articles de blog et fils de débordement de pile traitant de ce problème exactement identique (par exemple - This ou This ), le premier extrait qui soulève cette exception devrait fonctionner, car fieldfilter et fieldmap sont initialisés en tant que pointeurs sur les interfaces, plutôt que sur la valeur des interfaces. Je ne suis pas parvenu à comprendre ce qui se passe réellement ici et que je dois changer pour que je ne déclare pas une FieldInterface et n'assigne l'implémentation de cette interface. Il doit y avoir une manière élégante de faire ceci.

43
0rka

Donc, vous confondez deux concepts ici. Un pointeur sur une structure et un pointeur sur une interface ne sont pas identiques. Une interface peut stocker directement une structure ou un pointeur sur une structure. Dans ce dernier cas, vous utilisez toujours l'interface directement, not un pointeur sur l'interface. Par exemple:

type Fooer interface {
    Foo()
}

type Foo struct{}

func (f Foo) Foo() {}

func main() {
    var f1 Foo
    var f2 *Foo = &Foo{}

    DoFoo(f1)
    DoFoo(f2)
}

func DoFoo(f Fooer) {
    fmt.Printf("[%T] %+v\n", f, f)
}

Sortie: 

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

https://play.golang.org/p/BGV9d1-IRW

Dans les deux cas, la variable f dans DoFoo est simplement une interface, not un pointeur sur une interface. Cependant, lors du stockage de f2, l'interface détient un pointeur sur une structure Foo.

Les pointeurs vers les interfaces sont presque jamais utiles. En fait, le runtime Go a été spécifiquement modifié de 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 sur une interface reflète un malentendu sur la manière dont les interfaces sont censées fonctionner.

Cependant, il existe une limitation sur les interfaces. Si vous transmettez une structure directement dans une interface, seules les méthodes valeur de ce type (c'est-à-dire func (f Foo) Foo() et non func (f *Foo) Foo()) peuvent être utilisées pour remplir l'interface. En effet, vous stockez une copie de la structure d'origine dans l'interface. Par conséquent, les méthodes de pointeur auraient des effets inattendus (incapables de modifier la structure d'origine). Ainsi, la règle de base par défaut est de stocker les pointeurs sur les structures dans les interfaces, à moins d'une raison impérieuse de ne pas le faire.

Spécifiquement 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. fieldfilter est du type *FieldFilter, qui remplit le type d'interface FilterInterface, et donc AddFilter l'acceptera.

Voici quelques bonnes références pour comprendre le fonctionnement et l'intégration des méthodes, des types et des interfaces dans Go:

69
Kaedys
GetFilterByID(i uuid.UUID) *FilterInterface

Lorsque j'obtiens cette erreur, c'est généralement parce que je spécifie un pointeur sur une interface plutôt que sur une interface (ce sera en fait un pointeur sur ma structure qui remplit l'interface). 

Il y a une utilisation valable pour * interface {...} mais plus généralement, je pense simplement: "ceci est un pointeur" au lieu de "ceci est une interface qui se trouve être un pointeur dans le code que j'écris"

Le simple fait de la lancer parce que la réponse acceptée, bien que détaillée, ne m'a pas aidé à résoudre les problèmes.

0
Dan Farrell