X tidak mengimplementasikan Y (... metode memiliki penerima pointer) [ditutup]

201

Sudah ada beberapa T&J pada hal " X ini tidak mengimplementasikan Y (... metode memiliki penerima pointer) ", tetapi bagi saya, mereka tampaknya berbicara tentang hal-hal yang berbeda, dan tidak berlaku untuk kasus khusus saya.

Jadi, alih-alih membuat pertanyaan menjadi sangat spesifik, saya membuatnya luas dan abstrak - Sepertinya ada beberapa kasus berbeda yang dapat membuat kesalahan ini terjadi, bisakah seseorang meringkasnya?

Yaitu, bagaimana menghindari masalah, dan jika itu terjadi, apa kemungkinannya? Terima kasih.

xpt
sumber

Jawaban:

365

Kesalahan waktu kompilasi ini muncul ketika Anda mencoba untuk menetapkan atau meneruskan (atau mengkonversi) tipe konkret ke tipe antarmuka; dan tipe itu sendiri tidak mengimplementasikan antarmuka, hanya pointer ke tipe .

Mari kita lihat sebuah contoh:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

func (m *MyType) String() string { return m.value }

The Stringerjenis antarmuka memiliki satu metode saja: String(). Nilai apa pun yang disimpan dalam nilai antarmuka Stringerharus memiliki metode ini. Kami juga menciptakan MyType, dan kami menciptakan metode MyType.String()dengan penerima pointer . Ini berarti String()metode adalah dalam metode set dari *MyTypejenis, tapi tidak dalam dari MyType.

Ketika kami mencoba untuk menetapkan nilai MyTypeke variabel tipe Stringer, kami mendapatkan kesalahan dalam pertanyaan:

m := MyType{value: "something"}

var s Stringer
s = m // cannot use m (type MyType) as type Stringer in assignment:
      //   MyType does not implement Stringer (String method has pointer receiver)

Tapi semuanya baik-baik saja jika kita mencoba menetapkan nilai tipe *MyTypeke Stringer:

s = &m
fmt.Println(s)

Dan kami mendapatkan hasil yang diharapkan (coba di Go Playground ):

something

Jadi persyaratan untuk mendapatkan kesalahan waktu kompilasi ini:

  • Nilai tipe beton non-pointer yang ditugaskan (atau diteruskan atau dikonversi)
  • Jenis antarmuka yang ditugaskan ke (atau diteruskan ke, atau dikonversi ke)
  • Jenis beton memiliki metode antarmuka yang diperlukan, tetapi dengan penerima pointer

Kemungkinan untuk menyelesaikan masalah:

  • Pointer ke nilai harus digunakan, yang set metodenya akan menyertakan metode dengan penerima pointer
  • Atau tipe penerima harus diubah menjadi non-pointer , sehingga set metode tipe beton non-pointer juga akan berisi metode (dan dengan demikian memenuhi antarmuka). Ini mungkin atau mungkin tidak layak, seolah-olah metode harus mengubah nilai, penerima non-pointer bukan pilihan.

Structs dan embedding

Saat menggunakan struct dan embedding , seringkali bukan "Anda" yang mengimplementasikan antarmuka (menyediakan implementasi metode), tetapi tipe yang Anda sematkan di struct. Seperti dalam contoh ini:

type MyType2 struct {
    MyType
}

m := MyType{value: "something"}
m2 := MyType2{MyType: m}

var s Stringer
s = m2 // Compile-time error again

Sekali lagi, kompilasi kesalahan waktu, karena kumpulan metode MyType2tidak berisi String()metode yang disematkan MyType, hanya kumpulan metode *MyType2, sehingga yang berikut berfungsi (coba di Go Playground ):

var s Stringer
s = &m2

Kami juga dapat membuatnya berfungsi, jika kami menyematkan *MyTypedan hanya menggunakan non-pointer MyType2 (coba di Go Playground ):

type MyType2 struct {
    *MyType
}

m := MyType{value: "something"}
m2 := MyType2{MyType: &m}

var s Stringer
s = m2

Juga, apa pun yang kami tanamkan (salah satu MyTypeatau *MyType), jika kami menggunakan pointer *MyType2, itu akan selalu berfungsi (coba di Go Playground ):

type MyType2 struct {
    *MyType
}

m := MyType{value: "something"}
m2 := MyType2{MyType: &m}

var s Stringer
s = &m2

Bagian yang relevan dari spec (dari bagian Struct types ):

Diberikan tipe struct Sdan tipe bernama T, metode yang dipromosikan termasuk dalam set metode struct sebagai berikut:

  • Jika Sberisi bidang anonim T, set metode Sdan *Skeduanya termasuk metode yang dipromosikan dengan penerima T. Set metode *Sjuga termasuk metode yang dipromosikan dengan penerima *T.
  • Jika Sberisi bidang anonim *T, set metode Sdan *Skeduanya termasuk metode yang dipromosikan dengan penerima Tatau *T.

Jadi dengan kata lain: jika kita menanamkan tipe non-pointer, set metode dari non-pointer embedder hanya mendapatkan metode dengan penerima non-pointer (dari tipe tertanam).

Jika kita menanamkan tipe pointer, set metode dari embedder non-pointer mendapat metode dengan penerima pointer dan non-pointer (dari tipe tertanam).

Jika kita menggunakan nilai pointer ke embedder, terlepas dari apakah tipe yang disematkan adalah pointer atau tidak, set metode pointer ke embedder selalu mendapatkan metode dengan penerima pointer dan non-pointer (dari tipe embedded).

catatan:

Ada kasus yang sangat mirip, yaitu ketika Anda memiliki nilai antarmuka yang membungkus nilai MyType, dan Anda mencoba mengetikkan menegaskan nilai antarmuka lain dari itu Stringer,. Dalam hal ini pernyataan tidak akan berlaku karena alasan yang dijelaskan di atas, tetapi kami mendapatkan kesalahan runtime yang sedikit berbeda:

m := MyType{value: "something"}

var i interface{} = m
fmt.Println(i.(Stringer))

Runtime panic (coba di Go Playground ):

panic: interface conversion: main.MyType is not main.Stringer:
    missing method String

Mencoba mengonversi alih-alih mengetikkan pernyataan, kita mendapatkan kesalahan waktu kompilasi yang sedang kita bicarakan:

m := MyType{value: "something"}

fmt.Println(Stringer(m))
icza
sumber
Terima kasih atas jawaban yang sangat komprehensif. Maaf karena terlambat merespons karena anehnya saya tidak mendapatkan pemberitahuan SO. Satu kasus yang saya cari, jawabannya adalah "fungsi anggota" harus berupa semua tipe penunjuk, misalnya, " func (m *MyType)", atau tidak ada . Benarkah begitu? Bisakah saya mencampur berbagai jenis "fungsi anggota", misalnya, func (m *MyType)& func (m MyType)?
xpt
3
@xpt Anda dapat menggabungkan penerima pointer dan non-pointer, itu bukan keharusan untuk membuat semua sama. Hanya aneh jika Anda memiliki 19 metode dengan penerima pointer dan Anda membuat satu dengan penerima non-pointer. Ini juga membuat lebih sulit untuk melacak metode mana yang merupakan bagian dari metode tipe mana yang ditetapkan jika Anda mulai mencampurnya. Lebih detail dalam jawaban ini: Nilai penerima vs penerima Pointer di Golang?
icza
Bagaimana Anda benar-benar menyelesaikan masalah yang disebutkan di bagian akhir dalam "Catatan:" dengan antarmuka {} yang membungkus nilai MyType, jika Anda tidak dapat mengubah MyTypemenggunakan metode nilai. Saya mencoba sesuatu seperti ini (&i).(*Stringer)tetapi tidak berhasil. Apakah itu mungkin?
Joel Edström
1
@ JoelEdström Ya, itu mungkin, tetapi tidak masuk akal. Sebagai contoh, Anda dapat mengetik-menegaskan nilai dari jenis non-pointer dan menyimpannya dalam variabel, misalnya x := i.(MyType), dan kemudian Anda dapat memanggil metode dengan penerima pointer di atasnya, misalnya i.String(), yang merupakan singkatan (&i).String()yang berhasil karena variabel dialamatkan. Tetapi metode pointer mengubah nilai (nilai runcing) tidak akan tercermin dalam nilai yang dibungkus dengan nilai antarmuka, itu sebabnya tidak masuk akal.
icza
1
@DeepNightTwo Metode *Ttidak termasuk dalam set metode Skarena Smungkin tidak dapat dialamatkan (misalnya nilai pengembalian fungsi atau hasil pengindeksan peta), dan juga karena sering kali hanya salinan yang ada / diterima, dan jika mengambil alamatnya diizinkan, metode ini dengan penerima pointer hanya bisa memodifikasi salinan (Anda akan menganggap aslinya diubah). Lihat jawaban ini sebagai contoh: Menggunakan refleksi SetString .
icza
33

Agar singkat, katakan Anda memiliki kode ini dan Anda memiliki antarmuka Loader dan WebLoader yang mengimplementasikan antarmuka ini.

package main

import "fmt"

// Loader defines a content loader
type Loader interface {
    Load(src string) string
}

// WebLoader is a web content loader
type WebLoader struct{}

// Load loads the content of a page
func (w *WebLoader) Load(src string) string {
    return fmt.Sprintf("I loaded this page %s", src)
}

func main() {
    webLoader := WebLoader{}
    loadContent(webLoader)
}

func loadContent(loader Loader) {
    loader.Load("google.com")
}

Jadi kode ini akan memberi Anda kesalahan waktu kompilasi ini

./main.go:20:13: tidak dapat menggunakan webLoader (ketik WebLoader) sebagai tipe Loader dalam argumen untuk memuatContent: WebLoader tidak mengimplementasikan Loader (Metode memuat memiliki penerima pointer)

Jadi yang perlu Anda lakukan hanyalah mengubah webLoader := WebLoader{}ke berikut:

webLoader := &WebLoader{} 

Jadi mengapa itu akan diperbaiki karena Anda mendefinisikan fungsi ini func (w *WebLoader) Loaduntuk menerima penerima pointer. Untuk penjelasan lebih lanjut silakan baca jawaban @icza dan @karora

Saman Shafigh
sumber
6
Sejauh ini, inilah komentar termudah untuk dipahami. Dan langsung memecahkan masalah yang saya hadapi ..
Maxs728
@ Maxs728 Setuju, cukup jarang dalam jawaban untuk banyak masalah Go.
milosmns
6

Kasus lain ketika saya melihat hal semacam ini terjadi adalah jika saya ingin membuat antarmuka di mana beberapa metode akan mengubah nilai internal dan yang lain tidak.

type GetterSetter interface {
   GetVal() int
   SetVal(x int) int
}

Sesuatu yang kemudian mengimplementasikan antarmuka ini bisa seperti:

type MyTypeA struct {
   a int
}

func (m MyTypeA) GetVal() int {
   return a
}

func (m *MyTypeA) SetVal(newVal int) int {
    int oldVal = m.a
    m.a = newVal
    return oldVal
}

Jadi tipe implementasi kemungkinan akan memiliki beberapa metode yang merupakan penerima pointer dan beberapa yang tidak dan karena saya memiliki cukup beragam dari berbagai hal yang GetterSetters saya ingin memeriksa dalam tes saya bahwa mereka semua melakukan yang diharapkan.

Jika saya melakukan sesuatu seperti ini:

myTypeInstance := MyType{ 7 }
... maybe some code doing other stuff ...
var f interface{} = myTypeInstance
_, ok := f.(GetterSetter)
if !ok {
    t.Fail()
}

Maka saya tidak akan mendapatkan kesalahan "X tidak mengimplementasikan Y (metode Z memiliki penerima pointer)" yang disebutkan di atas (karena ini adalah kesalahan waktu kompilasi) tapi saya akan mengalami hari yang buruk memburu persis mengapa pengujian saya gagal .. .

Sebagai gantinya saya harus memastikan saya melakukan pengecekan tipe menggunakan pointer, seperti:

var f interface{} = new(&MyTypeA)
 ...

Atau:

myTypeInstance := MyType{ 7 }
var f interface{} = &myTypeInstance
...

Maka semua senang dengan tes!

Tapi tunggu! Dalam kode saya, mungkin saya memiliki metode yang menerima GetterSetter di suatu tempat:

func SomeStuff(g GetterSetter, x int) int {
    if x > 10 {
        return g.GetVal() + 1
    }
    return g.GetVal()
}

Jika saya memanggil metode ini dari dalam metode tipe lain, ini akan menghasilkan kesalahan:

func (m MyTypeA) OtherThing(x int) {
    SomeStuff(m, x)
}

Salah satu dari panggilan berikut akan berfungsi:

func (m *MyTypeA) OtherThing(x int) {
    SomeStuff(m, x)
}

func (m MyTypeA) OtherThing(x int) {
    SomeStuff(&m, x)
}
karora
sumber