Плутанина "<type> - вказівник на інтерфейс, а не інтерфейс"


104

Шановні колеги-розробники,

У мене є ця проблема, яка мені здається трохи дивною. Погляньте на цей фрагмент коду:

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, і mapma поля ініціалізуються як вказівники на інтерфейси, а не значення інтерфейси. Я не зміг обернути голову навколо того, що насправді відбувається тут, що мені потрібно змінити, щоб я не оголосив FieldInterface і призначив реалізацію для цього інтерфейсу. Має бути елегантний спосіб зробити це.


При переході * FilterInterfaceдо FilterInterfaceЛінії _ = filtermap.AddFilter(fieldfilter)Тепер піднімає це: не може використовувати fieldfilter (типу coreinterfaces.FieldFilter) як тип coreinterfaces.FilterInterface в аргументі filtermap.AddFilter: coreinterfaces.FieldFilter не реалізує coreinterfaces.FilterInterface (метод фільтрації має приймач покажчика) Однак при зміні напрямку рядок до _ = filtermap.AddFilter(&fieldfilter)нього працює. Що тут відбувається? чому так?
0rka

2
Тому що методи, що реалізують інтерфейс, мають приймачі покажчиків. Передаючи значення, він не реалізує інтерфейс; передаючи вказівник, це робить, тому що застосовуються методи. Взагалі кажучи, маючи справу з інтерфейсами, ви передаєте вказівник на структуру функції, яка очікує інтерфейс. Ви майже ніколи не хочете вказувати на інтерфейс в будь-якому сценарії.
Адріан

1
Я розумію вашу думку, але, змінюючи значення параметра з * FilterInterfaceструктури, яка реалізує цей інтерфейс, це порушує ідею передачі інтерфейсів функціям. Що я хотів досягти, це не прив’язаність до тієї структури, яку я проходив, а скоріше будь-яка структура, що реалізує інтерфейс, який мені цікаво використовувати. Будь-які зміни коду, на вашу думку, можна зробити більш ефективними або відповідати стандартам? Я буду радий скористатися послугами з перегляду коду :)
0rka

2
Ваша функція повинна приймати аргумент інтерфейсу (не вказівник на інтерфейс). Абонент повинен перейти вказівник до структури, яка реалізує інтерфейс. Це не "порушує ідею передачі інтерфейсів функціям" - функція все-таки бере інтерфейс, ви передаєте конкретизацію, яка реалізує інтерфейс.
Адріан

Відповіді:


140

Отже, ви плутаєте тут два поняття. Вказівник на структуру та вказівник на інтерфейс не є однаковим. Інтерфейс може зберігати або структуру безпосередньо, або вказівник на структуру. В останньому випадку ви все одно просто використовуєте інтерфейс безпосередньо, а не вказівник на інтерфейс. Наприклад:

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:


"Це відбувається тому, що ви зберігаєте копію оригінальної структури в інтерфейсі, тому методи покажчика матимуть несподівані ефекти (тобто не в змозі змінити оригінальну структуру)" - це не має сенсу як причина обмеження. Зрештою, єдина копія, можливо, весь час зберігалася в інтерфейсі.
WPWoodJr

Ваша відповідь там не має сенсу. Ви припускаєте, що розташування, в якому конкретний тип, що зберігається в інтерфейсі, не змінюється, коли ви змінюєте те, що там зберігається, а це не так, і це повинно бути очевидним, якщо ви зберігаєте щось із іншим компонуванням пам'яті. Що ви не отримуєте щодо мого коментаря вказівника, це те, що метод приймача вказівника конкретного типу завжди може змінювати приймач, на який він викликається. Значення, збережене в інтерфейсі, примушує копію, на яку ви не можете отримати посилання, тому приймачі покажчиків не можуть змінювати початковий період.
Kaedys

5
GetFilterByID(i uuid.UUID) *FilterInterface

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

Існує дійсне використання * інтерфейсу {...}, але частіше я просто думаю "це вказівник", а не "це інтерфейс, який буває вказівником у коді, який я пишу"

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.