X nie implementuje Y (metoda… ma odbiornik wskazujący) [zamknięty]

201

Jest już kilka pytań i odpowiedzi na ten temat „ X nie implementuje Y (metoda ma odbiornik wskaźnika) ”, ale wydaje mi się, że mówią o różnych rzeczach i nie odnoszą się do mojego konkretnego przypadku.

Zamiast więc sprecyzować pytanie, robię je szerokie i abstrakcyjne - Wydaje się, że istnieje kilka różnych przypadków, w których może się zdarzyć ten błąd, czy ktoś mógłby to podsumować?

Tj. Jak uniknąć problemu, a jeśli się pojawi, jakie są możliwości? Dzięki.

xpt
źródło

Odpowiedzi:

365

Ten błąd czasu kompilacji pojawia się, gdy próbujesz przypisać lub przekazać (lub przekonwertować) konkretny typ na typ interfejsu; a sam typ nie implementuje interfejsu, a jedynie wskaźnik do typu .

Zobaczmy przykład:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

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

StringerTypu interfejs ma tylko jedną metodę: String(). Każda wartość przechowywana w wartości interfejsu Stringermusi mieć tę metodę. Stworzyliśmy również MyTypei stworzyliśmy metodę MyType.String()z odbiornikiem wskaźnika . Oznacza to, że String()metoda ta jest w zestawie metody w *MyTyperodzaju, ale nie na tym, że od MyType.

Kiedy próbujemy przypisać wartość MyTypezmiennej typu Stringer, otrzymujemy następujący błąd:

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)

Ale wszystko jest w porządku, jeśli spróbujemy przypisać wartość typu *MyTypedo Stringer:

s = &m
fmt.Println(s)

I otrzymujemy oczekiwany wynik (wypróbuj go na Go Playground ):

something

Więc wymagania, aby uzyskać ten błąd czasu kompilacji:

  • Przypisywana (lub przekazywana lub konwertowana) wartość typu non-point konkretnych typów
  • Typ interfejsu przypisywany (przekazywany do lub konwertowany na)
  • Konkretny typ ma wymaganą metodę interfejsu, ale z odbiornikiem wskaźnika

Możliwości rozwiązania problemu:

  • Należy użyć wskaźnika do wartości, którego zestaw metod będzie obejmował metodę z odbiornikiem wskaźnika
  • Lub typ odbiornika musi zostać zmieniony na non-pointer , więc zestaw metod typu non-point-beton będzie również zawierał metodę (a tym samym spełnia interfejs). Może to być, ale nie musi, opłacalne, ponieważ jeśli metoda musi zmodyfikować wartość, odbiornik bez wskaźnika nie jest opcją.

Struktury i osadzanie

Podczas używania struktur i osadzania często nie jest to „ty”, który implementuje interfejs (zapewnia implementację metody), ale typ, który osadzasz w swoim struct. Jak w tym przykładzie:

type MyType2 struct {
    MyType
}

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

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

Znowu błąd czasu kompilacji, ponieważ zestaw metod MyType2nie zawiera String()metody osadzonej MyType, tylko zestaw metod *MyType2, więc następujące działania (wypróbuj na Go Playground ):

var s Stringer
s = &m2

Możemy również sprawić, że będzie działał, jeśli osadzimy *MyTypei użyjemy tylko wskaźnika innego MyType2 (wypróbuj go na Go Playground ):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = m2

Ponadto, niezależnie od tego, co osadzimy (albo, MyTypealbo *MyType), jeśli użyjemy wskaźnika *MyType2, zawsze będzie działać (wypróbuj go na Go Playground ):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = &m2

Odpowiednia sekcja ze specyfikacji (z sekcji Typy konstrukcji ):

Biorąc pod uwagę typ struktury Si nazwany typ T, promowane metody są zawarte w zestawie metod struktury w następujący sposób:

  • Jeśli Szawiera anonimowe pole T, zestawy metod Si *Soba zawierają promowane metody z odbiornikiem T. Zestaw metod *Sobejmuje również promowane metody z odbiornikiem *T.
  • Jeśli Szawiera anonimowe pole *T, zestawy metod Si *Soba zawierają promowane metody z odbiornikiem Tlub *T.

Innymi słowy: jeśli osadzimy typ nieinterpretacyjny, zestaw metod embedera nieinterpretacyjnego pobiera metody tylko z odbiornikami non-pointer (z typu osadzonego).

Jeśli osadzimy typ wskaźnika, zestaw metod modułu osadzającego niebędącego wskaźnikiem pobiera metody zarówno z odbiornikami wskaźnikowymi, jak i nie wskaźnikowymi (z typu osadzonego).

Jeśli użyjemy wartości wskaźnika do narzędzia osadzającego, niezależnie od tego, czy typ osadzony jest wskaźnikiem, czy nie, zestaw metod wskaźnika do narzędzia osadzającego zawsze pobiera metody zarówno z odbiornikiem wskaźnika, jak i odbiornika niebędącego wskaźnikiem (z typu osadzonego).

Uwaga:

Jest bardzo podobny przypadek, a mianowicie, gdy mają wartość interfejsu, który otacza wartość MyTypei spróbować wpisać assert inną wartość interfejsu z nim Stringer. W takim przypadku twierdzenie nie zostanie zachowane z powodów opisanych powyżej, ale otrzymamy nieco inny błąd wykonania:

m := MyType{value: "something"}

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

Runtime panic (wypróbuj na Go Playground ):

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

Próbując przekonwertować zamiast assert, otrzymujemy błąd kompilacji, o którym mówimy:

m := MyType{value: "something"}

fmt.Println(Stringer(m))
icza
źródło
Dzięki za niezwykle wyczerpującą odpowiedź. Przepraszam za późną odpowiedź, ponieważ dziwnie nie otrzymałem powiadomienia SO. W jednym przypadku, którego szukałem, odpowiedź była taka, że ​​„funkcje składowe” powinny być albo wszystkimi typami wskaźników, np. „ func (m *MyType)”, Albo żadnymi . Czy tak jest Czy mogę łączyć różne rodzaje „funkcji składowych”, np. func (m *MyType)& func (m MyType)?
xpt
3
@xpt Możesz mieszać odbiorniki ze wskaźnikiem i bez wskaźnika, nie jest wymagane, aby wszystko było takie samo. To po prostu dziwne, jeśli masz 19 metod z odbiornikiem wskaźnika i robisz jedną z odbiornikiem bez wskaźnika. Utrudnia także śledzenie, które metody są częścią zestawów metod poszczególnych typów, jeśli zaczniesz je miksować. Więcej szczegółów w tej odpowiedzi: Odbiorca wartości vs. Odbiornik wskaźnika w Golang?
icza
Jak faktycznie rozwiązujesz problem wymieniony na końcu w „Uwaga:” z interfejsem {} zawijającym wartość MyType, jeśli nie możesz zmienić MyTypemetody wartości. Próbowałem czegoś takiego, (&i).(*Stringer)ale to nie działa. Czy to w ogóle możliwe?
Joel Edström,
1
@ JoelEdström Tak, jest to możliwe, ale nie ma sensu. Na przykład możesz wpisać-potwierdzić wartość typu niepochodzącego ze wskaźnika i zapisać ją w zmiennej, np. x := i.(MyType)A następnie możesz wywoływać metody z odbiornikiem wskaźnika, np. i.String()Co jest skrótem, dla (&i).String()którego udaje się, ponieważ zmienne są adresowalne. Ale metoda wskaźnika zmieniająca wartość (wskazaną wartość) nie zostanie odzwierciedlona w wartości opakowanej w wartość interfejsu, dlatego nie ma to większego sensu.
icza
1
@DeepNightTwo Metody *Tnie są zawarte w zestawie metod, Sponieważ Smogą nie być adresowalne (np. Wartość zwracana przez funkcję lub wynik indeksowania mapy), a także dlatego, że często jest dostępna / odbierana tylko kopia, a jeśli przyjmowanie jej adresu jest dozwolone, metoda ze wskaźnikiem odbiornik może tylko modyfikować kopię (zamieszanie, jakbyś założył, że oryginał został zmodyfikowany). Zobacz tę odpowiedź na przykład: Korzystanie z odbicia SetString .
icza
33

Krótko mówiąc, powiedzmy, że masz ten kod i masz interfejs modułu ładującego oraz moduł WebLoader, który implementuje ten interfejs.

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

Więc ten kod da ci ten błąd czasu kompilacji

./main.go:20:13: nie można użyć webLoader (typ WebLoader) jako typu Loader jako argumentu loadContent: WebLoader nie implementuje modułu ładującego (metoda ładowania ma odbiornik wskaźnika)

Musisz tylko zmienić webLoader := WebLoader{}na następujące:

webLoader := &WebLoader{} 

Dlaczego więc to naprawi, ponieważ zdefiniujesz tę funkcję, func (w *WebLoader) Loadaby zaakceptować odbiornik wskaźnika. Aby uzyskać więcej wyjaśnień, przeczytaj odpowiedzi @icza i @karora

Saman Shafigh
źródło
6
Zdecydowanie był to najłatwiejszy do zrozumienia komentarz. I bezpośrednio rozwiązałem problem, z którym się spotkałem
Maxs728,
@ Maxs728 Uzgodniony, dość rzadki w odpowiedziach na wiele problemów Go.
milosmns
6

Innym przypadkiem, gdy widziałem, że coś takiego się dzieje, jest to, że chcę utworzyć interfejs, w którym niektóre metody zmodyfikują wartość wewnętrzną, a inne nie.

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

Coś, co następnie implementuje ten interfejs, może wyglądać tak:

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
}

Tak więc typ implementacyjny prawdopodobnie będzie miał pewne metody, które są odbiornikami wskaźników, a niektóre nie, a ponieważ mam dość różnorodne te różne rzeczy, które są GetterSetters, chciałbym sprawdzić w moich testach, czy wszystkie wykonują oczekiwane.

Gdybym miał zrobić coś takiego:

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

Wtedy nie dostanę wyżej wspomnianego błędu „X nie implementuje Y (metoda Z ma odbiornik wskaźnika)” (ponieważ jest to błąd czasu kompilacji), ale będę miał zły dzień, ścigając dokładnie, dlaczego mój test się nie udaje. .

Zamiast tego muszę się upewnić, że sprawdzam typ za pomocą wskaźnika, takiego jak:

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

Lub:

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

Wtedy wszystko jest zadowolone z testów!

Ale poczekaj! W moim kodzie być może mam metody, które gdzieś akceptują GetterSetter:

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

Jeśli wywołam te metody z wewnątrz metody innego typu, wygeneruje to błąd:

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

Każde z poniższych połączeń będzie działać:

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

func (m MyTypeA) OtherThing(x int) {
    SomeStuff(&m, x)
}
karora
źródło