„<typ> jest wskaźnikiem do interfejsu, a nie interfejsu”

105

Drodzy programiści,

Mam ten problem, który wydaje mi się trochę dziwny. Spójrz na ten fragment kodu:

package coreinterfaces

type FilterInterface interface {
    Filter(s *string) bool
}

type FieldFilter struct {
    Key string
    Val string
}

func (ff *FieldFilter) Filter(s *string) bool {
    // Some code
}

type FilterMapInterface interface {
    AddFilter(f *FilterInterface) uuid.UUID     
    RemoveFilter(i uuid.UUID)                   
    GetFilterByID(i uuid.UUID) *FilterInterface
}

type FilterMap struct {
    mutex   sync.Mutex
    Filters map[uuid.UUID]FilterInterface
}

func (fp *FilterMap) AddFilter(f *FilterInterface) uuid.UUID {
    // Some code
}

func (fp *FilterMap) RemoveFilter(i uuid.UUID) {
    // Some code
}

func (fp *FilterMap) GetFilterByID(i uuid.UUID) *FilterInterface {
    // Some code
}

Na innym pakiecie mam następujący kod:

func DoFilter() {
    fieldfilter := &coreinterfaces.FieldFilter{Key: "app", Val: "152511"}
    filtermap := &coreinterfaces.FilterMap{}
    _ = filtermap.AddFilter(fieldfilter) // <--- Exception is raised here
}

Środowisko wykonawcze nie zaakceptuje wspomnianej linii, ponieważ

„nie można użyć fieldfilter (type * coreinterfaces.FieldFilter) jako type * coreinterfaces.FilterInterface w argumencie do fieldint.AddFilter: * coreinterfaces.FilterInterface jest wskaźnikiem do interfejsu, a nie interfejsu”

Jednak przy zmianie kodu na:

func DoBid() error {
    bs := string(b)
    var ifilterfield coreinterfaces.FilterInterface
    fieldfilter := &coreinterfaces.FieldFilter{Key: "app", Val: "152511"}
    ifilterfield = fieldfilter
    filtermap := &coreinterfaces.FilterMap{}
    _ = filtermap.AddFilter(&ifilterfield)
}

Wszystko jest w porządku, a podczas debugowania aplikacji naprawdę wydaje się, że zawiera

Jestem trochę zdezorientowany w tym temacie. Patrząc na inne posty na blogu i wątki przepełnienia stosu omawiające dokładnie ten sam problem (na przykład - This lub This ), pierwszy fragment kodu, który wywołuje ten wyjątek, powinien działać, ponieważ zarówno fieldfilter, jak i fieldmap są inicjowane jako wskaźniki do interfejsów, a nie wartość interfejsy. Nie byłem w stanie ogarnąć tego, co właściwie się tutaj dzieje, co muszę zmienić, aby nie zadeklarować FieldInterface i przypisać implementacji dla tego interfejsu. Musi być na to elegancki sposób.

0rka
źródło
Po zmianie * FilterInterfacena FilterInterfaceThe line _ = filtermap.AddFilter(fieldfilter)teraz podnosi to: nie można użyć fieldfilter (typ coreinterfaces.FieldFilter) jako typ coreinterfaces.FilterInterface w argumencie do filtermap.AddFilter: coreinterfaces.FieldFilter nie implementuje coreinterfaces.FilterInterface (metoda filtru ma odbiornik wskaźnika) Jednak podczas zmiany linia do _ = filtermap.AddFilter(&fieldfilter)tego działa. co się tutaj stało? dlaczego?
0rka
2
Ponieważ metody implementujące interfejs mają odbiorniki wskaźników. Przekazując wartość, nie implementuje interfejsu; przekazuje wskaźnik, robi, ponieważ metody są następnie stosowane. Mówiąc ogólnie, gdy mamy do czynienia z interfejsami, przekazujemy wskaźnik do struktury do funkcji, która oczekuje interfejsu. Prawie nigdy nie chcesz mieć wskaźnika do interfejsu w żadnym scenariuszu.
Adrian
1
Rozumiem twój punkt widzenia, ale zmiana wartości parametru z * FilterInterfacena strukturę, która implementuje ten interfejs, łamie ideę przekazywania interfejsów do funkcji. To, co chciałem osiągnąć, to nie bycie związanym z tą strukturą, którą mijałem, ale raczej każdą strukturą, która implementuje interfejs, którego chcę użyć. Jakieś zmiany w kodzie, które uważasz za bardziej wydajne lub zgodne ze standardami, które powinienem wykonać? Z przyjemnością skorzystam z usług przeglądu kodu :)
0rka
2
Twoja funkcja powinna akceptować argument interfejsu (nie wskaźnik do interfejsu). Obiekt wywołujący powinien przekazać wskaźnik do struktury, która implementuje interfejs. To nie "łamie idei przekazywania interfejsów do funkcji" - funkcja nadal przyjmuje interfejs, przekazujesz konkrecję, która implementuje interfejs.
Adrian

Odpowiedzi:

143

Więc mylisz tutaj dwie koncepcje. Wskaźnik do struktury i wskaźnik do interfejsu to nie to samo. Interfejs może przechowywać bezpośrednio strukturę lub wskaźnik do struktury. W tym drugim przypadku nadal używasz interfejsu bezpośrednio, a nie wskaźnika do interfejsu. Na przykład:

type Fooer interface {
    Dummy()
}

type Foo struct{}

func (f Foo) Dummy() {}

func main() {
    var f1 Foo
    var f2 *Foo = &Foo{}

    DoFoo(f1)
    DoFoo(f2)
}

func DoFoo(f Fooer) {
    fmt.Printf("[%T] %+v\n", f, f)
}

Wynik:

[main.Foo] {}
[*main.Foo] &{}

https://play.golang.org/p/I7H_pv5H3Xl

W obu przypadkach fzmienna in DoFoojest tylko interfejsem, a nie wskaźnikiem do interfejsu. Jednak podczas przechowywania f2interfejs zawiera wskaźnik do Foostruktury.

Wskaźniki do interfejsów prawie nigdy nie są przydatne. W rzeczywistości środowisko uruchomieniowe Go zostało specjalnie zmienione kilka wersji z powrotem, aby nie były już automatycznie wyłuskiwane wskaźniki interfejsu (tak jak w przypadku wskaźników struktury), aby zniechęcić do ich używania. W przeważającej większości przypadków wskaźnik do interfejsu odzwierciedla niezrozumienie tego, jak powinny działać interfejsy.

Istnieje jednak ograniczenie dotyczące interfejsów. Jeśli przekażesz strukturę bezpośrednio do interfejsu, tylko metody wartości tego typu (tj. func (f Foo) Dummy()Nie func (f *Foo) Dummy()) mogą być używane do wypełnienia interfejsu. Dzieje się tak, ponieważ przechowujesz kopię oryginalnej struktury w interfejsie, więc metody wskaźnikowe miałyby nieoczekiwane efekty (tj. Nie byłyby w stanie zmienić oryginalnej struktury). Dlatego domyślną zasadą jest przechowywanie wskaźników do struktur w interfejsach , chyba że istnieje ważny powód, aby tego nie robić.

W szczególności za pomocą kodu, jeśli zmienisz podpis funkcji AddFilter na:

func (fp *FilterMap) AddFilter(f FilterInterface) uuid.UUID

Oraz podpis GetFilterByID do:

func (fp *FilterMap) GetFilterByID(i uuid.UUID) FilterInterface

Twój kod będzie działał zgodnie z oczekiwaniami. fieldfilterjest typu *FieldFilter, który wypełnia FilterInterfacetyp interfejsu, a tym samym AddFiltergo zaakceptuje.

Oto kilka dobrych odniesień do zrozumienia, jak metody, typy i interfejsy działają i integrują się ze sobą w Go:

Kaedys
źródło
„Dzieje się tak, ponieważ przechowujesz kopię oryginalnej struktury w interfejsie, więc metody wskaźnikowe miałyby nieoczekiwane efekty (tj. Nie byłyby w stanie zmienić oryginalnej struktury)” - to nie ma sensu jako przyczyna ograniczenia. W końcu jedyna kopia mogła być cały czas przechowywana w interfejsie.
WPWoodJr
Twoja odpowiedź nie ma sensu. Zakładasz, że lokalizacja, w której konkretny typ przechowywany w interfejsie, nie zmienia się, gdy zmieniasz to, co jest tam przechowywane, co nie ma miejsca i powinno być oczywiste, jeśli przechowujesz coś z innym układem pamięci. To, czego nie rozumiesz w moim komentarzu dotyczącym wskaźnika, to fakt, że metoda odbiornika wskaźnika na konkretnym typie może zawsze modyfikować odbiornik, na którym jest wywoływana. Wartość przechowywana w interfejsie wymusza kopię, do której nie można uzyskać odniesienia, więc odbiorcy wskaźników nie mogą modyfikować oryginalnej kropki.
Kaedys
5
GetFilterByID(i uuid.UUID) *FilterInterface

Kiedy otrzymuję ten błąd, zwykle dzieje się tak, ponieważ określam wskaźnik do interfejsu zamiast interfejsu (który w rzeczywistości będzie wskaźnikiem do mojej struktury, która spełnia interfejs).

* Interfejs {...} ma poprawne zastosowanie, ale częściej myślę po prostu „to jest wskaźnik” zamiast „to jest interfejs, który jest wskaźnikiem w kodzie, który piszę”

Po prostu wyrzucenie go tam, ponieważ zaakceptowana odpowiedź, choć szczegółowa, nie pomogła mi w rozwiązaniu problemu.

Daniel Farrell
źródło