web-dev-qa-db-fra.com

X n'implémente pas Y (... la méthode a un récepteur de pointeur)

Il y a déjà plusieurs questions et réponses sur cette chose "X ne met pas en œuvre Y (... la méthode a un pointeur récepteur)", mais pour moi, ils semblent parler de choses différentes et ne s'appliquent pas à mon cas spécifique.

Donc, au lieu de rendre la question très spécifique, je la résume de manière large et abstraite - on dirait qu’il existe plusieurs cas qui peuvent provoquer cette erreur, quelqu'un peut-il la résumer s'il vous plaît?

Comment éviter le problème, et si cela se produit, quelles sont les possibilités? Merci.

142
xpt

Cette erreur de compilation survient lorsque vous essayez d'affecter ou de transmettre (ou de convertir) un type concret à un type d'interface; et le type lui-même n'implémente pas l'interface, seulement un pointeur sur le type .

Voyons un exemple:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

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

Le type d'interface Stringer a une seule méthode: String(). Toute valeur stockée dans une valeur d'interface Stringer doit avoir cette méthode. Nous avons également créé un MyType et une méthode MyType.String() avec pointeur récepteur. Cela signifie que la méthode String() est dans le ensemble de méthodes du type *MyType, mais pas dans celui de MyType.

Lorsque nous essayons d'affecter une valeur de MyType à une variable de type Stringer, nous obtenons l'erreur en question:

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)

Mais tout va bien si nous essayons d’attribuer une valeur de type *MyType à Stringer:

s = &m
fmt.Println(s)

Et nous obtenons le résultat attendu (essayez-le sur le Go Playground ):

something

Donc, les conditions pour obtenir cette erreur de compilation:

  • Une valeur de type concret non-pointeur assignée (ou passée ou convertie)
  • Un type d'interface affecté (ou transmis ou converti)
  • Le type concret a la méthode requise de l'interface, mais avec un récepteur de pointeur

Possibilités de résolution du problème:

  • Un pointeur sur la valeur doit être utilisé, dont le jeu de méthodes inclura la méthode avec le pointeur récepteur.
  • Sinon, le type de destinataire doit être changé en non-pointeur , de sorte que l'ensemble de méthodes du type concret non-pointeur contiendra également la méthode (et satisfera ainsi l'interface). Cela peut ou peut ne pas être viable, car si la méthode doit modifier la valeur, un récepteur sans pointeur n'est pas une option.

Structures et intégration

Lors de l'utilisation de structures et incorporation , ce n'est souvent pas vous qui implémentez une interface (fournissez une implémentation de méthode), mais un type que vous intégrez dans votre struct. Comme dans cet exemple:

type MyType2 struct {
    MyType
}

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

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

Encore une fois, erreur de compilation, car le jeu de méthodes de MyType2 ne contient pas la méthode String() du MyType incorporé, mais seulement le jeu de méthodes de *MyType2, de sorte que sur le Go Playground ):

var s Stringer
s = &m2

Nous pouvons également le faire fonctionner, si nous intégrons *MyType et en utilisant uniquement un non-pointeur MyType2 (essayez-le sur le Go Playground ):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = m2

De plus, quoi que nous incorporions (MyType ou *MyType), si nous utilisons un pointeur *MyType2, cela fonctionnera toujours (essayez-le sur le Aller au terrain de je ):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = &m2

Section pertinente de la spécification (de la section Types de structures ):

Etant donné un type de structure S et un type nommé T, les méthodes promues sont incluses dans le jeu de méthodes de la structure comme suit:

  • Si S contient un champ anonyme T, les ensembles de méthodes de S et *S incluent tous deux des méthodes promues avec le destinataire T. L'ensemble de méthodes de *S inclut également les méthodes promues avec le destinataire *T.
  • Si S contient un champ anonyme *T, les ensembles de méthodes de S et *S incluent tous deux des méthodes promues avec le destinataire T ou *T.

Donc, en d'autres termes: si nous incorporons un type sans pointeur, l'ensemble de méthodes de l'embeddeur sans pointeur n'obtient que les méthodes avec des destinataires sans pointeur (du type incorporé).

Si nous incorporons un type de pointeur, le jeu de méthodes de l'embeddeur sans pointeur obtient des méthodes avec des destinataires à la fois pointeur et non pointeur (du type incorporé).

Si nous utilisons une valeur de pointeur sur l'embedder, que le type incorporé soit ou non un pointeur, l'ensemble de méthodes du pointeur sur l'embedder obtient toujours des méthodes avec les destinataires pointeur et non pointeur (du type incorporé).

Remarque:

Le cas est très similaire, à savoir lorsque vous avez une valeur d'interface qui enveloppe une valeur de MyType et que vous essayez de type assert une autre valeur d'interface, Stringer. Dans ce cas, l'assertion ne tiendra pas pour les raisons décrites ci-dessus, mais nous obtenons une erreur d'exécution légèrement différente:

m := MyType{value: "something"}

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

Panique à l'exécution (essayez-le sur le Go Playground ):

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

En essayant de convertir au lieu du type assert, nous obtenons l'erreur de compilation dont il est question:

m := MyType{value: "something"}

fmt.Println(Stringer(m))
269
icza

Pour faire court, disons que vous avez ce code et que vous avez une interface Loader et un WebLoader qui implémente cette interface.

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

Donc, ce code vous donnera cette erreur de temps de compilation

./main.go:20:13: impossible d'utiliser webLoader (type WebLoader) en tant que type Loader dans un argument pour loadContent: WebLoader n'implémente pas Loader (la méthode Load a un récepteur de pointeur)

Il vous suffit donc de modifier webLoader := WebLoader{} comme suit:

webLoader := &WebLoader{} 

Alors, pourquoi ça va résoudre parce que vous définissez cette fonction func (w *WebLoader) Load pour accepter un récepteur de pointeur. Pour plus d'explications, veuillez lire les réponses @icza et @karora

16
Saman Shafigh

Un autre cas où j'ai vu ce genre de chose se produire est si je veux créer une interface dans laquelle certaines méthodes modifieront une valeur interne et d'autres pas.

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

Quelque chose qui implémente cette interface pourrait être:

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
}

Donc, le type d'implémentation aura probablement des méthodes qui sont des récepteurs de pointeur et d'autres qui ne le sont pas. Comme j'ai une grande variété de ces choses qui sont des GetterSetters, j'aimerais vérifier dans mes tests qu'ils font tous les résultats attendus.

Si je devais faire quelque chose comme ça:

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

Ensuite, je n’aurai pas l’erreur susmentionnée "X n’implémente pas Y (la méthode Z a un récepteur de pointeur)" (mais c’est une erreur de compilation), mais je sera passe une mauvaise journée à courir exactement pourquoi mon test échoue ...

Au lieu de cela, je dois m'assurer que je vérifie le type à l'aide d'un pointeur, tel que:

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

Ou:

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

Alors tout est content des tests!

Mais attendez! Dans mon code, j'ai peut-être des méthodes qui acceptent un GetterSetter quelque part:

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

Si j'appelle ces méthodes depuis une autre méthode de type, ceci générera l'erreur:

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

L'un ou l'autre des appels suivants fonctionnera:

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

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