Kebingungan “<type> adalah penunjuk ke antarmuka, bukan antarmuka”

104

Rekan pengembang yang terhormat,

Saya punya masalah ini yang tampaknya agak aneh bagi saya. Lihat cuplikan kode ini:

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
}

Di beberapa paket lain, saya memiliki kode berikut:

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

Waktu proses tidak akan menerima baris yang disebutkan karena

"tidak dapat menggunakan fieldfilter (ketik * coreinterfaces.FieldFilter) sebagai jenis * coreinterfaces.FilterInterface dalam argumen ke fieldint.AddFilter: * coreinterfaces.FilterInterface adalah penunjuk ke antarmuka, bukan antarmuka"

Namun, saat mengubah kode menjadi:

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

Semuanya baik-baik saja dan saat men-debug aplikasi itu sepertinya benar-benar disertakan

Saya agak bingung dengan topik ini. Saat melihat posting blog lain dan utas stack overflow yang membahas masalah yang sama persis (misalnya - Ini , atau Ini ), potongan pertama yang memunculkan pengecualian ini seharusnya berfungsi, karena fieldfilter dan fieldmap diinisialisasi sebagai penunjuk ke antarmuka, bukan nilai antarmuka. Saya belum bisa memahami apa yang sebenarnya terjadi di sini yang perlu saya ubah agar saya tidak mendeklarasikan FieldInterface dan menetapkan implementasi untuk antarmuka itu. Harus ada cara yang elegan untuk melakukan ini.

0rka
sumber
Ketika mengubah * FilterInterfaceke FilterInterfaceThe line _ = filtermap.AddFilter(fieldfilter)now memunculkan ini: tidak dapat menggunakan fieldfilter (ketik coreinterfaces.FieldFilter) sebagai tipe coreinterfaces.FilterInterface dalam argumen ke filtermap.AddFilter: coreinterfaces.FieldFilter tidak mengimplementasikan coreinterfaces.FilterInterface (metode Filter memiliki penerima pointer) Namun ketika mengubah baris untuk _ = filtermap.AddFilter(&fieldfilter)bekerja. Apa yang terjadi di sini? mengapa demikian?
0rka
2
Karena metode yang mengimplementasikan antarmuka memiliki penerima penunjuk. Meneruskan nilai, itu tidak mengimplementasikan antarmuka; meneruskan pointer, itu dilakukan, karena metode kemudian berlaku. Secara umum, saat berhadapan dengan antarmuka, Anda meneruskan pointer ke struct ke fungsi yang mengharapkan antarmuka. Anda hampir tidak pernah menginginkan penunjuk ke antarmuka dalam skenario apa pun.
Adrian
1
Saya mengerti maksud Anda, tetapi dengan mengubah nilai parameter dari * FilterInterfacemenjadi struct yang mengimplementasikan antarmuka ini, ini mematahkan gagasan untuk meneruskan antarmuka ke fungsi. Apa yang ingin saya capai bukanlah terikat pada struktur apa yang saya lewati, melainkan apa saja struct yang mengimplementasikan antarmuka yang ingin saya gunakan. Adakah perubahan kode yang menurut Anda lebih efisien atau sesuai standar yang harus saya lakukan? Saya akan dengan senang hati menggunakan beberapa layanan peninjauan kode :)
0rka
2
Fungsi Anda harus menerima argumen antarmuka (bukan penunjuk ke antarmuka). Pemanggil harus meneruskan pointer ke struct yang mengimplementasikan antarmuka. Ini tidak "mematahkan gagasan untuk melewatkan antarmuka ke fungsi" - fungsi masih membutuhkan antarmuka, Anda meneruskan konkresi yang mengimplementasikan antarmuka.
Adrian

Jawaban:

140

Jadi Anda membingungkan dua konsep di sini. Sebuah pointer ke sebuah struct dan sebuah pointer ke sebuah antarmuka tidak sama. Antarmuka dapat menyimpan baik struct secara langsung atau penunjuk ke struct. Dalam kasus terakhir, Anda masih menggunakan antarmuka secara langsung, bukan penunjuk ke antarmuka. Sebagai contoh:

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

Keluaran:

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

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

Dalam kedua kasus, fvariabel dalam DoFoohanyalah sebuah antarmuka, bukan sebuah penunjuk ke sebuah antarmuka. Namun, saat menyimpan f2, antarmuka menahan penunjuk ke fileFoo struktur.

Pointer ke antarmuka hampir tidak pernah ada berguna. Nyatanya, Go runtime secara khusus diubah beberapa versi kembali menjadi tidak lagi secara otomatis penunjuk antarmuka dereferensi (seperti yang dilakukannya untuk penunjuk struktur), untuk mencegah penggunaannya. Dalam sebagian besar kasus, penunjuk ke antarmuka mencerminkan kesalahpahaman tentang bagaimana antarmuka seharusnya bekerja.

Namun, ada batasan pada antarmuka. Jika Anda mengirimkan struktur secara langsung ke antarmuka, hanya metode nilai dari jenis itu (mis. func (f Foo) Dummy(), Bukanfunc (f *Foo) Dummy() ) yang dapat digunakan untuk memenuhi antarmuka. Ini karena Anda menyimpan salinan dari struktur asli di antarmuka, jadi metode penunjuk akan memiliki efek yang tidak diharapkan (mis. Tidak dapat mengubah struktur asli). Jadi, aturan umum default adalah menyimpan pointer ke struktur di antarmuka , kecuali ada alasan kuat untuk tidak melakukannya.

Khususnya dengan kode Anda, jika Anda mengubah tanda tangan fungsi AddFilter menjadi:

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

Dan tanda tangan GetFilterByID untuk:

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

Kode Anda akan berfungsi seperti yang diharapkan. fieldfilteradalah tipe *FieldFilter, yang memenuhi FilterInterfacetipe antarmuka, dan dengan demikian AddFilterakan menerimanya.

Berikut beberapa referensi yang bagus untuk memahami bagaimana metode, tipe, dan antarmuka bekerja dan terintegrasi satu sama lain di Go:

Kaedys
sumber
"Ini karena Anda menyimpan salinan dari struktur asli di antarmuka, jadi metode penunjuk akan memiliki efek yang tidak terduga (yaitu tidak dapat mengubah struktur asli)" - ini tidak masuk akal sebagai alasan untuk pembatasan. Bagaimanapun, satu-satunya salinan mungkin telah disimpan di antarmuka selama ini.
WPWoodJr
Jawaban Anda tidak masuk akal. Anda berasumsi bahwa lokasi tempat jenis konkret yang disimpan dalam antarmuka tidak berubah saat Anda mengubah apa yang disimpan di sana, padahal sebenarnya tidak demikian, dan itu harus jelas jika Anda menyimpan sesuatu dengan tata letak memori yang berbeda. Apa yang tidak Anda dapatkan tentang komentar pointer saya adalah bahwa metode penerima pointer pada tipe konkret selalu dapat memodifikasi penerima yang dipanggil. Nilai yang disimpan dalam antarmuka memaksa salinan yang tidak bisa Anda rujuk, sehingga penerima penunjuk tidak bisa mengubah titik asli.
Kaedys
5
GetFilterByID(i uuid.UUID) *FilterInterface

Ketika saya mendapatkan kesalahan ini, biasanya karena saya menentukan penunjuk ke antarmuka daripada antarmuka (yang sebenarnya akan menjadi penunjuk ke struct saya yang memenuhi antarmuka).

Ada penggunaan yang valid untuk * antarmuka {...} tetapi lebih umum saya hanya berpikir 'ini adalah penunjuk' daripada 'ini adalah antarmuka yang kebetulan menjadi penunjuk dalam kode yang saya tulis'

Hanya membuangnya ke sana karena jawaban yang diterima, meskipun terperinci, tidak membantu saya memecahkan masalah.

Daniel Farrell
sumber