X n'implémente pas Y (… la méthode a un récepteur de pointeur) [fermé]

201

Il y a déjà plusieurs Q&R sur cette chose " X n'implémente pas Y (... la méthode a un récepteur de pointeur) ", mais pour moi, ils semblent parler de choses différentes, et ne pas s'appliquer à mon cas spécifique.

Donc, au lieu de rendre la question très précise, je la rend large et abstraite - Il semble que plusieurs cas différents peuvent provoquer cette erreur, quelqu'un peut-il la résumer s'il vous plaît?

C'est-à-dire, comment éviter le problème, et s'il se produit, quelles sont les possibilités? THX.

xpt
la source

Réponses:

365

Cette erreur de compilation se produit lorsque vous essayez d'affecter ou de passer (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 vers le type .

Voyons un exemple:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

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

Le Stringertype d'interface a une seule méthode: String(). Toute valeur stockée dans une valeur d'interface Stringerdoit avoir cette méthode. Nous avons également créé un MyType, et nous avons créé une méthode MyType.String()avec récepteur de pointeur . Cela signifie que la String()méthode se trouve dans l' ensemble de méthodes du *MyTypetype, mais pas dans celui de MyType.

Lorsque nous essayons d'attribuer 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 exigences pour obtenir cette erreur de compilation:

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

Possibilités de résoudre le problème:

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

Structures et encastrement

Lorsque vous utilisez des structures et l'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 au moment de la compilation, car l'ensemble de méthodes de MyType2ne contient pas la String()méthode de l'incorporé MyType, uniquement l'ensemble de méthodes de *MyType2, donc les opérations suivantes fonctionnent (essayez-les sur le Go Playground ):

var s Stringer
s = &m2

Nous pouvons également le faire fonctionner, si nous incorporons *MyTypeet utilisons 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 (soit MyTypeou *MyType), si nous utilisons un pointeur *MyType2, cela fonctionnera toujours (essayez-le sur le Go Playground ):

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

Étant donné un type de structure Set un type nommé T, les méthodes promues sont incluses dans l'ensemble de méthodes de la structure comme suit:

  • Si Scontient un champ anonyme T, les ensembles de méthodes de Set les *Sdeux incluent des méthodes promues avec le récepteur T. L'ensemble de *Sméthodes comprend également des méthodes promues avec récepteur *T.
  • Si Scontient un champ anonyme *T, les ensembles de méthodes de Set les *Sdeux incluent des méthodes promues avec le récepteur Tou *T.

En d'autres termes: si nous incorporons un type sans pointeur, l'ensemble de méthodes de l'embedder sans pointeur obtient uniquement les méthodes avec des récepteurs sans pointeur (du type incorporé).

Si nous incorporons un type pointeur, l'ensemble de méthodes de l'embedder non pointeur obtient des méthodes avec des récepteurs pointeurs et non pointeurs (du type incorporé).

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

Remarque:

Il existe un cas très similaire, à savoir lorsque vous avez une valeur d'interface qui encapsule une valeur de MyType, et que vous essayez de taper assert une autre valeur d'interface à partir de celle-ci 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

Tentative de conversion au lieu de type assert, nous obtenons l'erreur de compilation dont nous parlons:

m := MyType{value: "something"}

fmt.Println(Stringer(m))
icza
la source
Merci pour la réponse extrêmement complète. Désolé d'avoir répondu tard car étrangement je n'ai pas reçu la notification SO. Un cas que j'ai recherché, la réponse était que les "fonctions membres" devraient être tous les types de pointeurs, par exemple " func (m *MyType)", ou aucun . En est-il ainsi? Puis-je mélanger différents types de "fonctions membres", par exemple, func (m *MyType)& func (m MyType)?
xpt
3
@xpt Vous pouvez mélanger des récepteurs pointeurs et non pointeurs, ce n'est pas une obligation de faire tout de même. C'est juste bizarre si vous avez 19 méthodes avec récepteur pointeur et que vous en faites une avec récepteur non pointeur. Cela rend également plus difficile le suivi des méthodes faisant partie des ensembles de méthodes de quels types si vous commencez à les mélanger. Plus de détails dans cette réponse: Récepteur de valeur contre récepteur de pointeur à Golang?
icza
Comment résolvez-vous réellement le problème mentionné à la fin dans "Remarque:" avec une interface {} encapsulant une valeur de MyType, si vous ne pouvez pas changer MyTypepour utiliser des méthodes de valeur. J'ai essayé quelque chose comme ça (&i).(*Stringer)mais ça ne fonctionne pas. Est-ce même possible?
Joel Edström
1
@ JoelEdström Oui, c'est possible, mais cela n'a pas de sens. Par exemple, vous pouvez taper-affirmer la valeur du type non pointeur et la stocker dans une variable, par exemple x := i.(MyType), puis vous pouvez appeler des méthodes avec récepteur de pointeur dessus, par exemple i.String(), qui est un raccourci (&i).String()qui réussit car les variables sont adressables. Mais la méthode du pointeur modifiant la valeur (la valeur pointée) ne sera pas reflétée dans la valeur enveloppée dans la valeur d'interface, c'est pourquoi cela n'a pas de sens.
icza
1
@DeepNightTwo Les méthodes de *Tne sont pas incluses dans l'ensemble de méthodes Scar elles Speuvent ne pas être adressables (par exemple, la valeur de retour de la fonction ou le résultat de l'indexation de la carte), et aussi parce que souvent seule une copie est présente / reçue, et si la prise de son adresse est autorisée, la méthode avec le récepteur de pointeur ne pouvait que modifier la copie (confusion car vous supposeriez que l'original est modifié). Voir cette réponse pour un exemple: Utilisation de SetString de réflexion .
icza
33

Pour être bref, 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) comme type Loader dans l'argument de loadContent: WebLoader n'implémente pas Loader (la méthode Load a un récepteur de pointeur)

Donc, ce que vous avez seulement à faire est de changer webLoader := WebLoader{}comme suit:

webLoader := &WebLoader{} 

Alors pourquoi cela va se corriger car vous définissez cette fonction func (w *WebLoader) Loadpour accepter un récepteur de pointeur. Pour plus d'explications, veuillez lire les réponses @icza et @karora

Saman Shafigh
la source
6
C'était de loin le commentaire le plus facile à comprendre. Et directement résolu le problème auquel j'étais confronté ..
Maxs728
@ Maxs728 D'accord, assez rare dans les réponses aux nombreux problèmes de Go.
milosmns
6

Un autre cas où j'ai vu ce genre de chose se produire, c'est si je veux créer une interface où 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 alors cette interface pourrait être comme:

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 pointeurs et d'autres qui ne le sont pas et puisque j'ai une variété de ces différentes choses qui sont des GetterSetters, je voudrais vérifier dans mes tests qu'ils font tous ce qui est attendu.

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

Alors je ne vais pas obtenir ce qui précède « X ne met pas en oeuvre Y (méthode Z a récepteur pointeur) » erreur (car il est une erreur de compilation) mais je le ferai avoir une mauvaise journée pourchassant exactement pourquoi mon test échoue .. .

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

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

Ou:

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

Alors tout le monde 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, cela générera l'erreur:

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

L'un des appels suivants fonctionnera:

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

func (m MyTypeA) OtherThing(x int) {
    SomeStuff(&m, x)
}
Karora
la source