X не реалізує Y (… метод має приймач покажчика) [закрито]


201

У цій справі вже є декілька питань і запитань " X не реалізує Y (... метод має приймач вказівника) ", але мені здається, вони говорять про різні речі і не стосуються мого конкретного випадку.

Отже, замість того, щоб робити питання дуже конкретним, я роблю його широким та абстрактним. Здається, є кілька різних випадків, які можуть призвести до цієї помилки, чи може хтось підсумувати її, будь ласка?

Тобто, як уникнути проблеми, і якщо вона виникає, які є можливості? Дякую.

Відповіді:


365

Ця помилка часу компіляції виникає при спробі призначити або передати (або перетворити) конкретний тип типу інтерфейсу; і сам тип не реалізує інтерфейс, тільки покажчик типу .

Давайте подивимось приклад:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

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

StringerТип інтерфейсу має тільки один метод: String(). Будь-яке значення, яке зберігається у значенні інтерфейсу, Stringerповинно мати цей метод. Ми також створили a 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 ):

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

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = m2

Крім того, що б ми не вставили ( MyTypeабо *MyType), якщо ми будемо використовувати вказівник *MyType2, він завжди буде працювати (спробуйте на майданчику Go ):

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

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

Намагаючись перетворити замість типової ствердження, ми отримуємо помилку часу компіляції, про яку ми говоримо:

m := MyType{value: "something"}

fmt.Println(Stringer(m))

Дякую за надзвичайно вичерпну відповідь. Вибачте за те, що пізно реагували, як це не дивно, що я не отримав сповіщення про те. Один із випадків, які я шукав, відповідь полягала в тому, що "функції членів" повинні бути або всіма типами вказівників, наприклад, " func (m *MyType)", або жодними . Це так? Чи можна змішувати різні типи "функцій членів", наприклад, func (m *MyType)& func (m MyType)?
xpt

3
@xpt Ви можете змішувати вказівні та не вказівні приймачі, це не обов'язково робити все однаковими. Це просто дивно, якщо у вас є 19 методів з покажчиком вказівника, і ви використовуєте його з не-покажчиком. Це також ускладнює відстеження, які методи входять до складу методів тих типів, якщо ви почнете їх змішувати. Детальніше у цій відповіді: Приймач значення та приймач покажчиків у Голангу?
icza

Як ви насправді вирішуєте проблему, згадану наприкінці у "Примітці", за допомогою інтерфейсу {}, переносячи значення MyType, якщо ви не можете змінити MyTypeвикористання методів значення. Я спробував щось подібне, (&i).(*Stringer)але це не працює. Чи можливо це навіть?
Joel Edström

1
@ JoelEdström Так, це можливо, але це мало сенсу. Наприклад, ви можете набрати значення нетипового типу і зберегти його у змінній, наприклад x := i.(MyType), а потім ви можете викликати методи із вказівником на ньому, наприклад i.String(), це скорочення, для (&i).String()якого вдається, оскільки змінні адресовані. Але метод вказівника, що змінює значення (вказане значення), не відображатиметься у значенні, зафіксованому в інтерфейсному значенні, тому це мало сенсу.
icza

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) як тип завантажувача в аргументі 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)
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.