X не реализует Y (... у метода есть указатель получателя) [закрыто]

201

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

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

Т.е. как избежать проблемы, и если она возникает, каковы возможности? Спасибо.

XPT
источник

Ответы:

365

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

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

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переменной типа 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))
icza
источник
Спасибо за чрезвычайно полный ответ. Извините за поздний ответ, как ни странно, я не получил уведомление SO. В одном случае, который я искал, ответом было то, что «функции-члены» должны быть либо всех типов указателей, например, « func (m *MyType)», либо ни одного . Это так? Могу ли я смешивать различные типы «функций-членов», например func (m *MyType)& func (m MyType)?
xpt
3
@xpt Вы можете смешивать указатель и приемник без указателя, делать это не обязательно. Это просто странно, если у вас есть 19 методов с приемником указателя, а вы делаете один с приемником без указателя. Это также усложняет отслеживание того, какие методы являются частью набора методов каких типов, если вы начнете их смешивать. Более подробно в этом ответе: Получатель значения по сравнению с получателем указателя на Голанге?
2015 г.
Как вы на самом деле решаете проблему, упомянутую в конце в «Примечание:», с интерфейсом {}, заключающим значение MyType, если вы не можете перейти MyTypeна использование методов значения. Я пробовал что-то подобное, (&i).(*Stringer)но это не работает. Это вообще возможно?
Джоэл
1
@ JoelEdström Да, это возможно, но в этом нет особого смысла. Например, вы можете ввести-утвердить значение типа, не являющегося указателем, и сохранить его в переменной, например x := i.(MyType), и затем вы можете вызывать методы с получателем указателя, например i.String(), который является сокращением для (&i).String()которого успешно, потому что переменные являются адресуемыми. Но метод указателя, изменяющий значение (указанное значение), не будет отражен в значении, заключенном в значение интерфейса, поэтому он не имеет особого смысла.
17
1
@DeepNightTwo Методы *Tне включены в набор методов, Sпотому что они Sмогут быть не адресуемыми (например, возвращаемое значение функции или результат индексации карты), а также потому, что часто присутствует / принимается только копия, и если получение ее адреса разрешено, метод с указателем получатель может только изменить копию (путаница, поскольку вы предполагаете, что оригинал изменен). Посмотрите этот ответ для примера: Использование отражения SetString .
icza
33

Короче говоря, допустим, у вас есть этот код, и у вас есть интерфейс 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.

Саман Шафиг
источник
6
Безусловно, это был самый простой комментарий для понимания. И непосредственно решил проблему, с которой я столкнулся ..
Maxs728
@ Maxs728 Согласны, довольно редко встречаются ответы на многие проблемы Go.
milosmns
6

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

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)
}
karora
источник