X не реализует Y (... метод имеет указатель на приемник)

Уже есть несколько Q & As на этом "X не реализует Y (... метод имеет приемник указателя)", но для меня они, кажется, говорят о разных вещах и не применяют к моим конкретным случай.

Итак, вместо того, чтобы сделать вопрос очень конкретным, я делаю его широким и абстрактным - Кажется, что существует несколько разных случаев, которые могут привести к этой ошибке, может ли кто-нибудь обобщить ее, пожалуйста?

I.e., как избежать проблемы, и если это произойдет, каковы возможности? спасибо.

Ответ 1

Эта ошибка времени компиляции возникает, когда вы пытаетесь назначить или передать (или преобразовать) конкретный тип в тип интерфейса; и сам тип не реализует интерфейс, только указатель на тип.

Давайте посмотрим на пример:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

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

Тип интерфейса Stringer имеет только один метод: String(). Любое значение, которое хранится в значении интерфейса Stringer должно иметь этот метод. Мы также создали MyType и создали метод MyType.String() с получателем указателя. Это означает, что метод String() находится в наборе *MyType типа *MyType, но не в MyType.

Когда мы пытаемся присвоить значение MyType переменной типа Stringer, мы получаем соответствующую ошибку:

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)

Но все в порядке, если мы пытаемся присвоить значение типа *MyType для Stringer:

s = &m
fmt.Println(s)

И мы получаем ожидаемый результат (попробуйте на Go Playground):

something

Итак, требования для получения этой ошибки времени компиляции:

  • Значение не указательного конкретного типа, присваиваемого (или переданного или преобразованного)
  • Тип интерфейса, назначаемый (или переданный, или преобразованный в)
  • Конкретный тип имеет требуемый метод интерфейса, но с указателем приемника

Возможности решения вопроса:

  • Необходимо использовать указатель на значение, набор методов которого будет включать метод с получателем указателя
  • Или тип получателя должен быть изменен на не указатель, поэтому набор методов конкретного типа без указателя также будет содержать метод (и, таким образом, удовлетворять интерфейсу). Это может или не может быть жизнеспособным, так как если метод должен изменить значение, приемник без указателя не вариант.

Структуры и встраивание

При использовании структур и встраивания часто не "вы" реализуют интерфейс (предоставляют реализацию метода), а тип, который вы встраиваете в свою struct. Как в этом примере:

type MyType2 struct {
    MyType
}

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

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

Опять же, ошибка времени компиляции, потому что набор методов MyType2 не содержит метод String() встроенного MyType, а только набор методов *MyType2, поэтому работает следующее (попробуйте на Go Playground):

var s Stringer
s = &m2

Мы также можем заставить его работать, если встраиваем *MyType и используем только не указатель MyType2 (попробуйте на Go Playground):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = m2

Кроме того, независимо от того, что мы встраиваем (MyType или *MyType), если мы используем указатель *MyType2, он всегда будет работать (попробуйте на Go Playground):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = &m2

Соответствующий раздел из спецификации (из раздела Типы структур):

Учитывая тип структуры S и тип с именем T, продвигаемые методы включаются в набор методов структуры следующим образом:

  • Если S содержит анонимное поле T, наборы методов S и *S включают в себя повышенные методы с приемником T Набор методов *S также включает повышенные методы с приемником *T
  • Если S содержит анонимное поле *T, наборы методов S и *S включают в себя повышенные методы с получателем T или *T

Другими словами: если мы встраиваем тип без указателя, набор методов устройства для вставки без указателя получает только методы с получателями без указателя (из встроенного типа).

Если мы встраиваем тип указателя, набор методов устройства для вставки без указателя получает методы как с указателями, так и с получателями без указателя (из встроенного типа).

Если мы используем значение указателя для устройства для внедрения, независимо от того, является ли встроенный тип указателем или нет, набор методов указателя на устройство для внедрения всегда получает методы как с указателями, так и с получателями без указателей (из встроенного типа).

Замечания:

Существует очень похожий случай, а именно, когда у вас есть значение интерфейса, которое переносит значение MyType, и вы пытаетесь напечатать из него другое значение интерфейса, Stringer. В этом случае утверждение не будет выполнено по причинам, описанным выше, но мы получим немного другую ошибку времени выполнения:

m := MyType{value: "something"}

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

Паника во время выполнения (попробуйте на Go Playground):

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

Пытаясь преобразовать вместо типа assert, мы получаем ошибку времени компиляции, о которой мы говорим:

m := MyType{value: "something"}

fmt.Println(Stringer(m))

Ответ 2

Короче говоря, допустим, у вас есть этот код, и у вас есть интерфейс Loader и WebLoader, который реализует этот интерфейс.

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

Так что этот код даст вам эту ошибку времени компиляции

./main.go:20:13: нельзя использовать webLoader (тип WebLoader) в качестве типа Loader в аргументе loadContent: WebLoader не реализует Loader (метод Load имеет приемник указателя)

Так что вам нужно всего лишь изменить webLoader := WebLoader{} на следующее:

webLoader := &WebLoader{} 

Итак, почему это будет исправлено, потому что вы определяете эту функцию func (w *WebLoader) Load чтобы принять указатель на приемник. Для более подробного объяснения, пожалуйста, прочитайте ответы @icza и @karora

Ответ 3

Другой случай, когда я видел, что такое происходит, - это если я хочу создать интерфейс, в котором некоторые методы изменят внутреннее значение, а другие - нет.

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

То, что затем реализует этот интерфейс, может выглядеть так:

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
}

Таким образом, тип реализации, скорее всего, будет иметь некоторые методы, которые являются получателями указателей, а некоторые - нет, и, поскольку у меня есть множество этих различных вещей, которые являются GetterSetters, я хотел бы проверить в своих тестах, что они все выполняют ожидаемые.

Если бы я сделал что-то вроде этого:

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

Тогда я не получу вышеупомянутую ошибку "X не реализует Y (у метода Z есть указатель на приемник)" (так как это ошибка времени компиляции), но у меня будет плохой день, чтобы выяснить, почему мой тест терпит неудачу..,

Вместо этого я должен убедиться, что я делаю проверку типа с помощью указателя, например:

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

Или же:

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

Тогда все довольны тестами!

Но ждать! В моем коде, возможно, у меня есть методы, которые где-то принимают GetterSetter:

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

Если я вызову эти методы из другого метода типа, это сгенерирует ошибку:

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

Любой из следующих вызовов будет работать:

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

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