"<type> - это указатель на интерфейс, а не интерфейс" путаницы

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

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

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

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

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

Ответ 1

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

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

Ответ 2

GetFilterByID(i uuid.UUID) *FilterInterface

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

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

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