X no implementa Y (... el método tiene un receptor de puntero) [cerrado]

201

Ya hay varias preguntas y respuestas sobre esta cosa " X no implementa Y (... el método tiene un receptor de puntero) ", pero para mí, parecen estar hablando de cosas diferentes y no se aplican a mi caso específico.

Entonces, en lugar de hacer la pregunta muy específica, la hago amplia y abstracta: parece que hay varios casos diferentes que pueden hacer que este error ocurra, ¿alguien puede resumirlo por favor?

Es decir, cómo evitar el problema, y ​​si ocurre, ¿cuáles son las posibilidades? Gracias.

xpt
fuente

Respuestas:

365

Este error en tiempo de compilación surge cuando intenta asignar o pasar (o convertir) un tipo concreto a un tipo de interfaz; y el tipo en sí no implementa la interfaz, solo un puntero al tipo .

Veamos un ejemplo:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

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

El Stringertipo de interfaz tiene un solo método: String(). Cualquier valor almacenado en un valor de interfaz Stringerdebe tener este método. También creamos a MyType, y creamos un método MyType.String()con receptor de puntero . Esto significa que el String()método está en el conjunto de métodos del *MyTypetipo, pero no en el de MyType.

Cuando intentamos asignar un valor de MyTypea una variable de tipo Stringer, obtenemos el error en cuestión:

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)

Pero todo está bien si intentamos asignar un valor de tipo *MyTypea Stringer:

s = &m
fmt.Println(s)

Y obtenemos el resultado esperado (pruébalo en Go Playground ):

something

Entonces, los requisitos para obtener este error en tiempo de compilación:

  • Un valor de tipo concreto sin puntero que se asigna (o se pasa o se convierte)
  • Un tipo de interfaz que se asigna (o se pasa o se convierte)
  • El tipo concreto tiene el método requerido de la interfaz, pero con un receptor de puntero

Posibilidades para resolver el problema:

  • Se debe utilizar un puntero al valor, cuyo conjunto de métodos incluirá el método con el receptor del puntero
  • O bien, el tipo de receptor debe cambiarse a sin puntero , por lo que el conjunto de métodos del tipo concreto sin puntero también contendrá el método (y así satisfará la interfaz). Esto puede o no ser viable, ya que si el método tiene que modificar el valor, un receptor sin puntero no es una opción.

Estructuras e incrustaciones

Cuando se utilizan estructuras e incrustaciones , a menudo no es "usted" quien implementa una interfaz (proporcione una implementación de método), sino un tipo que incrusta en su struct. Como en este ejemplo:

type MyType2 struct {
    MyType
}

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

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

De nuevo, error en tiempo de compilación, porque el conjunto de métodos de MyType2no contiene el String()método de incrustado MyType, solo el conjunto de métodos de *MyType2, por lo que funciona lo siguiente (pruébelo en Go Playground ):

var s Stringer
s = &m2

También podemos hacer que funcione, si incrustamos *MyTypey usamos solo un no puntero MyType2 (pruébalo en Go Playground ):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = m2

Además, lo que sea que incrustamos (ya sea MyTypeo *MyType), si usamos un puntero *MyType2, siempre funcionará (pruébalo en Go Playground ):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = &m2

Sección relevante de la especificación (de la sección Tipos de estructura ):

Dado un tipo de estructura Sy un tipo con nombre T, los métodos promocionados se incluyen en el conjunto de métodos de la estructura de la siguiente manera:

  • Si Scontiene un campo en el anonimato T, los conjuntos de métodos de Sy *Sambos incluyen métodos con el receptor promovido T. El conjunto de *Smétodos también incluye métodos promocionados con receptor *T.
  • Si Scontiene un campo anónimo *T, los conjuntos de método de Sy *Sambos incluyen métodos con receptor promovido To *T.

En otras palabras: si incrustamos un tipo sin puntero, el conjunto de métodos del incrustador sin puntero solo obtiene los métodos con receptores sin puntero (del tipo incrustado).

Si incrustamos un tipo de puntero, el conjunto de métodos del incrustador sin puntero obtiene métodos con receptores punteros y no punteros (del tipo incrustado).

Si usamos un valor de puntero para el integrador, independientemente de si el tipo incrustado es puntero o no, el conjunto de métodos del puntero para el integrador siempre obtiene métodos con receptores punteros y no punteros (del tipo incrustado).

Nota:

Existe un caso muy similar, es decir, cuando tiene un valor de interfaz que envuelve un valor de MyTypee intenta escribir afirmar otro valor de interfaz a partir de él Stringer,. En este caso, la afirmación no se mantendrá por los motivos descritos anteriormente, pero obtenemos un error de tiempo de ejecución ligeramente diferente:

m := MyType{value: "something"}

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

Pánico en tiempo de ejecución (pruébalo en Go Playground ):

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

Intentando convertir en lugar de escribir aserción, obtenemos el error en tiempo de compilación del que estamos hablando:

m := MyType{value: "something"}

fmt.Println(Stringer(m))
icza
fuente
Gracias por la respuesta extremadamente completa. Perdón por responder tarde porque extrañamente no recibí la notificación SO. Un caso que busqué, la respuesta fue que las "funciones miembro" deberían ser todos los tipos de puntero, por ejemplo, " func (m *MyType)" o ninguno . ¿Es tan? ¿Puedo mezclar diferentes tipos de "funciones miembro", por ejemplo, func (m *MyType)& func (m MyType)?
xpt
3
@xpt Puede mezclar receptores con puntero y sin puntero, no es un requisito hacer lo mismo. Es extraño si tienes 19 métodos con receptor de puntero y haces uno con receptor sin puntero. También hace que sea más difícil rastrear qué métodos son parte de los conjuntos de métodos de cada tipo si comienza a mezclarlos. Más detalles en esta respuesta: ¿ Receptor de valor vs. Receptor de puntero en Golang?
icza
¿Cómo resuelve realmente el problema mencionado al final en "Nota:" con una interfaz {} que ajusta un valor de MyType, si no puede cambiar MyTypepara usar métodos de valor? Intenté algo como esto (&i).(*Stringer)pero no funciona. ¿Es posible?
Joel Edström
1
@ JoelEdström Sí, es posible, pero tiene poco sentido. Por ejemplo, puede escribir-afirmar el valor del tipo sin puntero y almacenarlo en una variable, por ejemplo x := i.(MyType), y luego puede llamar a métodos con un receptor de puntero, por ejemplo i.String(), que es una abreviatura (&i).String()que tiene éxito porque las variables son direccionables. Pero el método de puntero que cambia el valor (el valor puntiagudo) no se reflejará en el valor envuelto en el valor de la interfaz, por eso tiene poco sentido.
icza 18/0717
1
@DeepNightTwo Los métodos de *Tno están incluidos en el conjunto de métodos Sporque Spodría no ser direccionable (por ejemplo, el valor de retorno de la función o el resultado de la indexación del mapa), y también porque a menudo solo está presente / recibida una copia, y si se permite tomar su dirección, el método con el receptor de puntero solo se podía modificar la copia (confusión como se supondría que se modifica el original) Vea esta respuesta para un ejemplo: Uso de la reflexión SetString .
icza
33

Para mantenerlo breve, supongamos que tiene este código y tiene una interfaz Loader y un WebLoader que implementa esta interfaz.

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

Entonces este código le dará este error de tiempo de compilación

./main.go:20:13: no se puede usar webLoader (tipo WebLoader) como tipo Loader en argumento para cargar el contenido: WebLoader no implementa Loader (el método de carga tiene receptor de puntero)

Entonces, lo que solo necesita hacer es cambiar webLoader := WebLoader{}a lo siguiente:

webLoader := &WebLoader{} 

Entonces, ¿por qué se solucionará porque define esta función func (w *WebLoader) Loadpara aceptar un receptor de puntero? Para obtener más explicaciones, lea las respuestas de @icza y @karora

Saman Shafigh
fuente
66
Con mucho, este fue el comentario más fácil de entender. Y resolvió directamente el problema al que me enfrentaba ...
Maxs728
@ Maxs728 De acuerdo, muy poco común en las respuestas a los muchos problemas de Go.
milosmns
6

Otro caso cuando he visto este tipo de cosas es si quiero crear una interfaz donde algunos métodos modificarán un valor interno y otros no.

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

Algo que luego implementa esta interfaz podría ser:

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
}

Por lo tanto, el tipo de implementación probablemente tendrá algunos métodos que son receptores de puntero y otros que no lo son, y dado que tengo una gran variedad de estas varias cosas que son GetterSetters, me gustaría comprobar en mis pruebas que todos están haciendo lo esperado.

Si tuviera que hacer algo como esto:

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

Entonces no voy a conseguir el mencionado "X no implementa Y (método de Z tiene el receptor puntero)" error (ya que es un error en tiempo de compilación) pero yo voy a tener un mal día persiguiendo a exactamente por qué mi prueba está fallando .. .

En cambio, tengo que asegurarme de hacer la verificación de tipo usando un puntero, como:

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

O:

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

¡Entonces todo está contento con las pruebas!

¡Pero espera! En mi código, tal vez tengo métodos que aceptan un GetterSetter en alguna parte:

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

Si llamo a estos métodos desde otro método de tipo, esto generará el error:

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

Cualquiera de las siguientes llamadas funcionará:

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

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