X não implementa Y (… o método possui um receptor de ponteiro) [fechado]

201

Já existem várias perguntas e respostas sobre esse assunto " X não implementa Y (... o método tem um receptor de ponteiro) ", mas para mim, eles parecem estar falando de coisas diferentes e não se aplicam ao meu caso específico.

Então, em vez de tornar a pergunta muito específica, eu a estou tornando ampla e abstrata - parece que existem vários casos diferentes que podem fazer esse erro acontecer, alguém pode resumir isso, por favor?

Ou seja, como evitar o problema e, se ocorrer, quais são as possibilidades? THX.

xpt
fonte

Respostas:

365

Esse erro em tempo de compilação ocorre quando você tenta atribuir ou passar (ou converter) um tipo concreto para um tipo de interface; e o próprio tipo não implementa a interface, apenas um ponteiro para o tipo .

Vamos ver um exemplo:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

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

O Stringertipo de interface tem apenas um método: String(). Qualquer valor armazenado em um valor de interface Stringerdeve ter esse método. Também criamos a MyType, e criamos um método MyType.String()com receptor de ponteiro . Isso significa que o String()método está no conjunto de métodos do *MyTypetipo, mas não no de MyType.

Quando tentamos atribuir um valor de MyTypea uma variável do tipo Stringer, obtemos o erro em questão:

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)

Mas está tudo bem se tentarmos atribuir um valor do tipo *MyTypepara Stringer:

s = &m
fmt.Println(s)

E obtemos o resultado esperado (experimente no Go Playground ):

something

Portanto, os requisitos para obter esse erro em tempo de compilação:

  • Um valor do tipo concreto não-ponteiro sendo atribuído (ou passado ou convertido)
  • Um tipo de interface que está sendo atribuído (ou passado a, ou convertido em)
  • O tipo concreto possui o método necessário da interface, mas com um receptor de ponteiro

Possibilidades de resolver o problema:

  • Um ponteiro para o valor deve ser usado, cujo conjunto de métodos incluirá o método com o receptor do ponteiro
  • Ou o tipo de receptor deve ser alterado para não ponteiro , para que o conjunto de métodos do tipo concreto não ponteiro também contenha o método (e, portanto, satisfaça a interface). Isso pode ou não ser viável, como se o método tivesse que modificar o valor, um receptor sem ponteiro não é uma opção.

Estruturas e incorporação

Ao usar estruturas e incorporação , geralmente não é "você" que implementa uma interface (fornece uma implementação de método), mas um tipo que você incorpora no seu struct. Como neste exemplo:

type MyType2 struct {
    MyType
}

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

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

Mais uma vez, erro em tempo de compilação, porque o conjunto de métodos MyType2não contém o String()método incorporado MyType, apenas o conjunto de métodos *MyType2, portanto, o seguinte funciona (tente no Go Playground ):

var s Stringer
s = &m2

Também podemos fazê-lo funcionar, se incorporarmos *MyTypee usarmos apenas um não ponteiro MyType2 (experimente no Go Playground ):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = m2

Além disso, o que quer que incorporemos ( MyTypeou *MyType), se usarmos um ponteiro *MyType2, ele sempre funcionará (tente no Go Playground ):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = &m2

Seção relevante da especificação (da seção Tipos de estrutura ):

Dado um tipo de estrutura Se um tipo chamado T, os métodos promovidos são incluídos no conjunto de métodos da estrutura, da seguinte maneira:

  • Se Scontiver um campo anônimo T, os conjuntos de métodos de Se *Sambos incluirão métodos promovidos com o receptor T. O conjunto de *Smétodos também inclui métodos promovidos com o receptor *T.
  • Se Scontiver um campo anônimo *T, os conjuntos de métodos de Se *Sambos incluirão métodos promovidos com receptor Tou *T.

Então, em outras palavras: se incorporarmos um tipo não ponteiro, o conjunto de métodos do incorporador não ponteiro só obtém os métodos com receptores não ponteiros (do tipo incorporado).

Se incorporarmos um tipo de ponteiro, o conjunto de métodos do incorporador não-ponteiro obterá métodos com receptores de ponteiro e não-ponteiro (do tipo incorporado).

Se usarmos um valor de ponteiro para o incorporador, independentemente de o tipo incorporado ser ponteiro ou não, o conjunto de métodos do ponteiro para o incorporador sempre obtém métodos com os receptores de ponteiro e não-ponteiro (do tipo incorporado).

Nota:

Existe um caso muito semelhante, a saber, quando você tem um valor de interface que envolve um valor MyTypee tenta digitar afirmar outro valor de interface Stringer,. Nesse caso, a afirmação não será válida pelos motivos descritos acima, mas obtemos um erro de tempo de execução ligeiramente diferente:

m := MyType{value: "something"}

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

Pânico no tempo de execução (experimente no Go Playground ):

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

Tentando converter em vez de declarar tipo, obtemos o erro em tempo de compilação sobre o qual estamos falando:

m := MyType{value: "something"}

fmt.Println(Stringer(m))
icza
fonte
Obrigado pela resposta extremamente abrangente. Desculpe por responder tarde, pois estranhamente não recebi a notificação do SO. Um caso que procurei, a resposta foi que as "funções-membro" deveriam ser todos os tipos de ponteiros, por exemplo, " func (m *MyType)" ou nenhum . É assim? Posso misturar diferentes tipos de "funções-membro", por exemplo, func (m *MyType)& func (m MyType)?
xpt
3
@xpt Você pode misturar receptores de ponteiro e não-ponteiro, não é necessário fazer o mesmo. É estranho se você tiver 19 métodos com receptor de ponteiro e você faz um com receptor sem ponteiro. Também torna mais difícil rastrear quais métodos fazem parte dos conjuntos de métodos de tipos, se você começar a misturá-los. Mais detalhes nesta resposta: Receptor de valor vs. Receptor de ponteiro em Golang?
icza 30/11/16
Como você realmente resolve o problema mencionado no final de "Nota:" com uma interface {} envolvendo um valor de MyType, se você não pode mudar MyTypepara usar métodos de valor. Eu tentei algo assim, (&i).(*Stringer)mas não está funcionando. Isso é possível?
Joel Edström
1
@ JoelEdström Sim, é possível, mas faz pouco sentido. Por exemplo, você pode digitar o valor do tipo não-ponteiro e armazená-lo em uma variável, por exemplo x := i.(MyType), e então pode chamar métodos com o receptor de ponteiro, por exemplo i.String(), que é uma abreviação para a (&i).String()qual é bem-sucedida porque as variáveis ​​são endereçáveis. Mas o método do ponteiro que altera o valor (o valor apontado) não será refletido no valor envolvido no valor da interface, por isso faz pouco sentido.
icza
1
@DeepNightTwo Os métodos de *Tnão estão incluídos no conjunto de métodos Sporque Spodem não ser endereçáveis ​​(por exemplo, valor de retorno da função ou resultado da indexação de mapas) e também porque geralmente apenas uma cópia está presente / recebida e, se o uso do endereço for permitido, o método com o ponteiro receptor só pode modificar a cópia (confusão como você supõe que o original seja modificado). Veja esta resposta para um exemplo: Usando reflexão SetString .
icza 22/09
33

Para mantê-lo breve, digamos que você tenha esse código e tenha uma interface do Loader e um WebLoader que implementem essa 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")
}

Portanto, este código fornecerá esse erro em tempo de compilação

./main.go:20:13: não é possível usar webLoader (digite WebLoader) como tipo Loader no argumento loadContent: O WebLoader não implementa o Loader (o método Load possui um receptor de ponteiro)

Então, o que você só precisa fazer é mudar webLoader := WebLoader{}para o seguinte:

webLoader := &WebLoader{} 

Então, por que isso será corrigido porque você define esta função func (w *WebLoader) Loadpara aceitar um receptor de ponteiro. Para mais explicações, leia as respostas @icza e @karora

Saman Shafigh
fonte
6
De longe, esse foi o comentário mais fácil de entender. E resolveu diretamente o problema que eu estava enfrentando ..
Maxs728 16/03/19
@ Maxs728 Concordou, bastante incomum nas respostas aos muitos problemas do Go.
milosmns
6

Outro caso em que vejo esse tipo de coisa acontecendo é se eu quero criar uma interface em que alguns métodos modifiquem um valor interno e outros não.

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

Algo que implementa essa interface pode ser como:

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
}

Portanto, o tipo de implementação provavelmente terá alguns métodos que são receptores de ponteiro e outros que não são, e como tenho várias dessas coisas que são GetterSetters, gostaria de verificar nos meus testes se todos estão fazendo o esperado.

Se eu fizesse algo assim:

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

Então eu não vou conseguir o já mencionado "X não implementar Y (método Z tem receptor ponteiro)" erro (uma vez que é um erro em tempo de compilação), mas eu vai ter um dia ruim perseguir exatamente por isso que o meu teste está falhando .. .

Em vez disso, tenho que me certificar de fazer a verificação de tipo usando um ponteiro, como:

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

Ou:

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

Então tudo está feliz com os testes!

Mas espere! No meu código, talvez eu tenha métodos que aceitem um GetterSetter em algum lugar:

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

Se eu chamar esses métodos de dentro de outro método de tipo, isso gerará o erro:

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

Qualquer uma das seguintes chamadas funcionará:

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

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