«<Тип> - это указатель на интерфейс, а не на интерфейс».

108

Уважаемые коллеги-разработчики!

У меня возникла эта проблема, которая мне кажется немного странной. Взгляните на этот фрагмент кода:

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
}

В каком-то другом пакете у меня есть следующий код:

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

Среда выполнения не примет указанную строку, потому что

«не может использовать fieldfilter (тип * coreinterfaces.FieldFilter) как тип * coreinterfaces.FilterInterface в аргументе fieldint.AddFilter: * coreinterfaces.FilterInterface - указатель на интерфейс, а не интерфейс»

Однако при изменении кода на:

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

Все в порядке и при отладке приложения действительно вроде включает

Я немного запутался в этой теме. При просмотре других сообщений в блогах и потоков переполнения стека, обсуждающих ту же самую проблему (например, This или This ), первый фрагмент, который вызывает это исключение, должен работать, потому что и fieldfilter, и fieldmap инициализируются как указатели на интерфейсы, а не как значение интерфейсы. Я не мог осмыслить то, что на самом деле здесь происходит, что мне нужно изменить, чтобы я не объявлял FieldInterface и не назначал реализацию для этого интерфейса. Должен быть изящный способ сделать это.

0rka
источник
При изменении * FilterInterfaceна FilterInterfaceСтрока _ = filtermap.AddFilter(fieldfilter)теперь вызывает следующее: нельзя использовать fieldfilter (тип coreinterfaces.FieldFilter) в качестве типа coreinterfaces.FilterInterface в аргументе filtermap.AddFilter: coreinterfaces.FieldFilter не реализует coreinterfaces.FilterInterface (метод фильтра имеет приемник указателя) Однако при изменении строка к _ = filtermap.AddFilter(&fieldfilter)нему работает. Что здесь происходит? это почему?
0rka 05
2
Потому что методы, реализующие интерфейс, имеют приемники указателей. Передавая значение, он не реализует интерфейс; передает указатель, так оно и есть, потому что затем применяются методы. Вообще говоря, имея дело с интерфейсами, вы передаете указатель на структуру функции, которая ожидает интерфейс. Вам почти никогда не понадобится указатель на интерфейс в любом сценарии.
Адриан
1
Я понимаю вашу точку зрения, но изменение значения параметра с * FilterInterfaceна структуру, реализующую этот интерфейс, нарушает идею передачи интерфейсов функциям. Я хотел не быть привязанным к той структуре, которую я передавал, а скорее к любой структуре, которая реализует интерфейс, который мне интересно использовать. Какие-либо изменения кода, которые вы считаете более эффективными или отвечающими стандартам, для меня? Я буду рад воспользоваться услугами проверки кода :)
0rka 05
2
Ваша функция должна принимать аргумент интерфейса (не указатель на интерфейс). Вызывающий должен передать указатель на структуру, реализующую интерфейс. Это не «нарушает идею передачи интерфейсов функциям» - функция по-прежнему принимает интерфейс, а вы передаете конкретную конструкцию, которая реализует интерфейс.
Адриан

Ответы:

145

Итак, вы путаете здесь два понятия. Указатель на структуру и указатель на интерфейс - это не одно и то же. Интерфейс может хранить либо структуру напрямую, либо указатель на структуру. В последнем случае вы по-прежнему просто используете интерфейс напрямую, а не указатель на интерфейс. Например:

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

Вывод:

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

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

В обоих случаях fпеременная in DoFoo- это просто интерфейс, а не указатель на интерфейс. Однако при сохранении f2интерфейс содержит указатель на Fooструктуру.

Указатели на интерфейсы почти никогда не используются. Фактически, среда выполнения Go была специально изменена в нескольких версиях, чтобы больше не было автоматического разыменования указателей интерфейсов (как это происходит с указателями структур), чтобы препятствовать их использованию. В подавляющем большинстве случаев указатель на интерфейс отражает неправильное понимание того, как интерфейсы должны работать.

Однако существует ограничение на интерфейсы. Если вы передаете структуру непосредственно в интерфейс, только методы значения этого типа (т. Е. func (f Foo) Dummy()Не func (f *Foo) Dummy()) могут использоваться для выполнения интерфейса. Это связано с тем, что вы храните копию исходной структуры в интерфейсе, поэтому методы указателя будут иметь неожиданные эффекты (т. Е. Не могут изменить исходную структуру). Таким образом, практическое правило по умолчанию - хранить указатели на структуры в интерфейсах , если нет веской причины не делать этого.

В частности, с вашим кодом, если вы измените подпись функции AddFilter на:

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

И подпись GetFilterByID для:

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

Ваш код будет работать должным образом. fieldfilterимеет тип *FieldFilter, который заполняет FilterInterfaceтип интерфейса и, таким образом AddFilter, принимает его.

Вот пара хороших ссылок для понимания того, как методы, типы и интерфейсы работают и интегрируются друг с другом в Go:

Kaedys
источник
«Это потому, что вы храните копию исходной структуры в интерфейсе, поэтому методы указателя будут иметь неожиданные эффекты (т. Е. Не смогут изменить исходную структуру)» - это не имеет смысла в качестве причины ограничения. В конце концов, единственная копия могла все время храниться в интерфейсе.
WPWoodJr
Ваш ответ не имеет смысла. Вы предполагаете, что место, в котором конкретный тип, хранящийся в интерфейсе, не изменяется, когда вы меняете то, что там хранится, что не так, и это должно быть очевидно, если вы храните что-то с другим макетом памяти. Чего вы не понимаете в моем комментарии к указателю, так это того, что метод приемника указателя для конкретного типа всегда может изменить приемник, для которого он вызывается. Значение, хранящееся в интерфейсе, заставляет копию, на которую вы не можете получить ссылку, поэтому получатели указателя не могут изменять исходную точку.
Kaedys
5
GetFilterByID(i uuid.UUID) *FilterInterface

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

Есть допустимое использование для * interface {...}, но чаще я просто думаю «это указатель» вместо «это интерфейс, который оказывается указателем в коде, который я пишу»

Просто выбросил его, потому что принятый ответ, хотя и подробный, не помог мне в устранении неполадок.

Дэниел Фаррелл
источник