X ไม่ได้ใช้ Y (…วิธีมีตัวรับสัญญาณตัวชี้) [ปิด]


202

มีคำถาม & คำตอบอยู่มากมายในเรื่อง " X ไม่ได้ใช้วิธี Y (... มีตัวรับสัญญาณตัวชี้) " แต่สำหรับฉันพวกเขาดูเหมือนจะพูดถึงสิ่งต่าง ๆ และไม่ได้ใช้กับกรณีเฉพาะของฉัน

ดังนั้นแทนที่จะทำคำถามที่เฉพาะเจาะจงฉันทำให้มันกว้างและเป็นนามธรรม - ดูเหมือนว่ามีหลายกรณีที่สามารถทำให้เกิดข้อผิดพลาดนี้ได้ใครบางคนสรุปได้ไหม

เช่นวิธีการหลีกเลี่ยงปัญหาและถ้ามันเกิดขึ้นสิ่งที่เป็นไปได้คืออะไร? ขอบคุณ.

คำตอบ:


371

ข้อผิดพลาดในการคอมไพล์เวลานี้เกิดขึ้นเมื่อคุณพยายามกำหนดหรือส่ง (หรือแปลง) ชนิดที่เป็นรูปธรรมให้กับประเภทอินเตอร์เฟส และประเภทของตัวเองไม่ได้ใช้อินเตอร์เฟซเพียงตัวชี้ไปยังชนิด

ลองดูตัวอย่าง:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

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

ประเภทอินเตอร์เฟซที่มีวิธีการหนึ่งเท่านั้น:Stringer String()ค่าใด ๆ ที่เก็บไว้ในค่าอินเทอร์เฟซStringerต้องมีวิธีนี้ นอกจากนี้เรายังสร้างMyTypeและเราสร้างวิธีที่MyType.String()มีตัวรับสัญญาณตัวชี้ ซึ่งหมายความว่าString()วิธีการที่อยู่ในวิธีการตั้งค่าของ*MyTypeประเภท MyTypeแต่ไม่ได้อยู่ในที่ของ

เมื่อเราพยายามกำหนดค่าMyTypeให้กับตัวแปรประเภทStringerเราได้รับข้อผิดพลาดดังกล่าว:

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)

แต่ทุกอย่างก็โอเคถ้าเราพยายามกำหนดค่าประเภท*MyTypeให้กับStringer:

s = &m
fmt.Println(s)

และเราได้ผลลัพธ์ตามที่คาดหวัง (ลองดูที่สนามเด็กเล่นโก ):

something

ดังนั้นข้อกำหนดในการรับข้อผิดพลาดในการรวบรวมนี้:

  • ค่าของชนิดคอนกรีตที่ไม่ใช่ตัวชี้กำลังถูกกำหนด (หรือผ่านหรือแปลง)
  • ประเภทอินเตอร์เฟสที่ถูกกำหนดให้ (หรือส่งผ่านไปยังหรือแปลงเป็น)
  • ชนิดคอนกรีตมีวิธีการที่จำเป็นของอินเทอร์เฟซ แต่มีตัวรับสัญญาณตัวชี้

ความเป็นไปได้ในการแก้ไขปัญหา:

  • ต้องใช้ตัวชี้ไปยังค่าซึ่งชุดวิธีการจะรวมวิธีการกับตัวรับสัญญาณตัวชี้
  • หรือประเภทผู้รับต้องเปลี่ยนเป็นไม่ใช่ตัวชี้ดังนั้นวิธีการตั้งค่าประเภทคอนกรีตไม่ชี้จะประกอบด้วยวิธี (และตอบสนองอินเทอร์เฟซ) สิ่งนี้อาจเป็นไปได้หรืออาจจะใช้ไม่ได้ราวกับว่าวิธีการนั้นมีการแก้ไขค่าตัวรับสัญญาณที่ไม่ใช่ตัวชี้นั้นไม่ใช่ตัวเลือก

โครงสร้างและการฝัง

เมื่อใช้structs และฝังมักจะเป็นไม่ได้ "คุณ" ที่ใช้อินเตอร์เฟซ (ให้ดำเนินการตามวิธีการ) structแต่ชนิดที่คุณสามารถฝังในของคุณ เหมือนในตัวอย่างนี้:

type MyType2 struct {
    MyType
}

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

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

อีกครั้งข้อผิดพลาดในการรวบรวมเพราะชุดวิธีการMyType2ไม่ได้มีString()วิธีการฝังตัวMyTypeเพียงชุดวิธีการ*MyType2ดังนั้นจึงทำงานต่อไปนี้ (ลองบนสนามเด็กเล่น Go ):

var s Stringer
s = &m2

เราสามารถทำให้มันใช้งานได้หากเราฝัง*MyTypeและใช้ตัวชี้ที่ไม่ใช่ตัวชี้ MyType2 (ลองใช้บนสนามเด็กเล่น Go ):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = m2

นอกจากนี้สิ่งที่เราฝัง (อย่างใดอย่างหนึ่งMyTypeหรือ*MyType) ถ้าเราใช้ตัวชี้*MyType2มันจะทำงานได้เสมอ (ลองบนสนามเด็กเล่น Go ):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = &m2

ส่วนที่เกี่ยวข้องจากข้อมูลจำเพาะ (จากหมวดโครงสร้างส่วน):

รับประเภท struct Sและชื่อประเภทTวิธีการเลื่อนระดับจะรวมอยู่ในชุดวิธีการ struct ดังนี้

  • หากSมีข้อมูลที่ไม่ระบุชื่อTวิธีการที่ชุดSและทั้งสองรวมถึงวิธีการส่งเสริมการลงทุนกับเครื่องรับ*S Tชุดวิธีการนอกจากนี้ยังมีวิธีการที่มีการรับการเลื่อนตำแหน่ง*S*T
  • หากSมีข้อมูลที่ไม่ระบุชื่อ*Tวิธีการที่ชุดSและ*Sทั้งสองรวมถึงวิธีการส่งเสริมการลงทุนกับเครื่องรับหรือT*T

ดังนั้นในคำอื่น ๆ : หากเราฝังประเภทที่ไม่ใช่ตัวชี้ชุดวิธีการฝังตัวไม่ใช่ชี้ได้รับวิธีการที่มีตัวรับสัญญาณที่ไม่ใช่ตัวชี้ (จากประเภทฝัง)

หากเราฝังชนิดตัวชี้ชุดวิธีของตัวฝังแบบไม่ใช่ตัวชี้จะรับเมธอดที่มีทั้งตัวชี้และตัวรับสัญญาณที่ไม่ใช่ตัวชี้ (จากชนิดแบบฝัง)

หากเราใช้ค่าตัวชี้ไปยังตัวฝังโดยไม่คำนึงว่าชนิดที่ฝังตัวเป็นตัวชี้หรือไม่ชุดวิธีการของตัวชี้ไปยังตัวฝังจะได้รับวิธีการที่มีทั้งตัวชี้และตัวรับที่ไม่ใช่ตัวชี้ (จากชนิดที่ฝังตัว)

บันทึก:

มีกรณีที่คล้ายกันมากคือคือเมื่อคุณมีค่าอินเตอร์เฟซที่ตัดค่าMyTypeและคุณพยายามที่จะพิมพ์ยืนยันStringerค่าอินเตอร์เฟซที่อื่นจากนั้น ในกรณีนี้การยืนยันจะไม่ถือด้วยเหตุผลที่อธิบายไว้ข้างต้น แต่เราได้รับข้อผิดพลาด runtime แตกต่างกันเล็กน้อย:

m := MyType{value: "something"}

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

รันไทม์ตกใจ (ลองไปที่สนามเด็กเล่นไป ):

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

ความพยายามในการแปลงแทนการยืนยันประเภทเราได้รับข้อผิดพลาดเวลาคอมไพล์ที่เรากำลังพูดถึง:

m := MyType{value: "something"}

fmt.Println(Stringer(m))

ขอบคุณสำหรับคำตอบที่ครอบคลุมอย่างยิ่ง ขออภัยที่ตอบช้าเพราะฉันไม่ได้รับการแจ้งเตือน กรณีหนึ่งที่ผมค้นหาคำตอบก็คือว่า "ฟังก์ชั่นสมาชิก" อย่างใดอย่างหนึ่งควรจะทุกประเภทตัวชี้เช่น " func (m *MyType)" หรือไม่มี มันเป็นอย่างนั้นเหรอ? ฉันสามารถผสม "ฟังก์ชั่นสมาชิก" ที่แตกต่างกันเช่นfunc (m *MyType)& func (m MyType)?
xpt

3
@xpt คุณสามารถผสมตัวชี้และตัวรับสัญญาณที่ไม่ใช่ตัวชี้ได้มันไม่ใช่ข้อกำหนดที่จะทำให้เหมือนกันทั้งหมด เป็นเรื่องแปลกถ้าคุณมี 19 วิธีด้วยตัวรับสัญญาณตัวชี้และคุณสร้างวิธีด้วยตัวรับสัญญาณที่ไม่ใช่ตัวชี้ นอกจากนี้ยังทำให้ยากต่อการติดตามว่าวิธีใดเป็นส่วนหนึ่งของวิธีการที่ประเภทตั้งไว้ถ้าคุณเริ่มผสมวิธีเหล่านั้น รายละเอียดเพิ่มเติมในคำตอบนี้: ตัวรับค่าเทียบกับตัวรับตัวชี้ใน Golang?
icza

คุณจะแก้ปัญหาที่กล่าวถึงในตอนท้ายได้อย่างไรใน "หมายเหตุ:" ด้วยอินเทอร์เฟซ {} การตัดค่าMyTypeหากคุณไม่สามารถเปลี่ยนMyTypeวิธีการใช้ค่าได้ ฉันลองอะไรเช่นนี้(&i).(*Stringer)แต่มันไม่ทำงาน เป็นไปได้ไหม
Joel Edström

1
@ JoelEdströmใช่เป็นไปได้ แต่ก็มีเหตุผลเล็กน้อย ตัวอย่างเช่นคุณอาจยืนยันประเภทของค่าที่ไม่ใช่ตัวชี้และเก็บไว้ในตัวแปรเช่นx := i.(MyType)และจากนั้นคุณสามารถเรียกวิธีการที่มีตัวรับสัญญาณชี้ไปที่มันเช่นi.String()ซึ่งเป็นชวเลข(&i).String()ที่ประสบความสำเร็จเพราะตัวแปรที่อยู่ แต่วิธีการของตัวชี้การเปลี่ยนค่า (ค่าที่แหลม) จะไม่ปรากฏในค่าที่ห่อในค่าของอินเทอร์เฟซนั่นคือเหตุผลที่มันสมเหตุสมผล
icza

1
@DeepNightT วิธีการสองรายการ*Tไม่รวมอยู่ในชุดวิธีการSเนื่องจากSอาจไม่สามารถระบุตำแหน่งได้ (เช่นค่าส่งคืนฟังก์ชันหรือผลลัพธ์ของการทำดัชนีแผนที่) และเพราะบ่อยครั้งมีเพียงสำเนา / ปัจจุบันและรับหากอนุญาตให้ใช้ที่อยู่ ด้วยตัวรับสัญญาณสามารถแก้ไขสำเนาได้เท่านั้น (ความสับสนตามที่คุณคิดว่าต้นฉบับถูกดัดแปลง) ดูคำตอบนี้สำหรับตัวอย่าง: การใช้ SetString
icza

33

เพื่อให้สั้นสมมติว่าคุณมีรหัสนี้และคุณมีอินเทอร์เฟซตัวโหลดและเว็บโหลดที่ใช้อินเทอร์เฟซนี้

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

ดังนั้นรหัสนี้จะทำให้คุณรวบรวมข้อผิดพลาดเวลานี้

./main.go:20:13: ไม่สามารถใช้ webLoader (พิมพ์ WebLoader) เป็นประเภท Loader ในอาร์กิวเมนต์เพื่อ loadContent: WebLoader ไม่ได้ใช้ Loader (วิธีการโหลดมีตัวรับตัวชี้)

ดังนั้นสิ่งที่คุณต้องทำคือเปลี่ยนwebLoader := WebLoader{}เป็นดังต่อไปนี้:

webLoader := &WebLoader{} 

เหตุใดจึงต้องแก้ไขเนื่องจากคุณกำหนดฟังก์ชันนี้func (w *WebLoader) Loadเพื่อยอมรับตัวรับสัญญาณ สำหรับคำอธิบายเพิ่มเติมโปรดอ่าน @icza และ @karora คำตอบ


6
นี่เป็นความคิดเห็นที่เข้าใจง่ายที่สุด และแก้ไขปัญหาที่ฉันเผชิญโดยตรง ..
Maxs728

@ Maxs728 ตกลงเห็นด้วยค่อนข้างแปลกในการตอบปัญหา Go มากมาย
milosmns

6

อีกกรณีหนึ่งเมื่อฉันเห็นสิ่งนี้เกิดขึ้นคือถ้าฉันต้องการสร้างอินเทอร์เฟซที่วิธีการบางอย่างจะแก้ไขค่าภายในและอื่น ๆ จะไม่

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

สิ่งที่ใช้อินเทอร์เฟซนี้อาจเป็นเช่นนั้น:

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
}

ดังนั้นประเภทของการนำไปใช้จะมีวิธีการบางอย่างที่เป็นตัวรับสัญญาณตัวชี้และบางอย่างที่ไม่ได้และเนื่องจากฉันมีสิ่งต่าง ๆ เหล่านี้มากมายที่เป็น GetterSetters ฉันต้องการตรวจสอบในแบบทดสอบของฉัน

ถ้าฉันจะทำอะไรแบบนี้:

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

จากนั้นฉันจะไม่ได้รับข้อความแสดงข้อผิดพลาด "X ไม่ได้ใช้ Y (วิธี Z มีตัวรับสัญญาณตัวชี้)" (เนื่องจากเป็นข้อผิดพลาดในการคอมไพล์เวลา) แต่ฉันจะมีวันที่ไม่ดีไล่ทำไมการทดสอบของฉันล้มเหลว .. .

แต่ฉันต้องแน่ใจว่าฉันทำการตรวจสอบประเภทโดยใช้ตัวชี้เช่น:

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

หรือ:

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

จากนั้นทั้งหมดมีความสุขกับการทดสอบ!

แต่เดี๋ยวก่อน! ในรหัสของฉันฉันอาจมีวิธีที่รับ GetterSetter ที่ไหนสักแห่ง:

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

ถ้าฉันเรียกวิธีการเหล่านี้จากภายในประเภทวิธีอื่นนี้จะสร้างข้อผิดพลาด:

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

การเรียกต่อไปนี้จะทำงาน:

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

func (m MyTypeA) OtherThing(x int) {
    SomeStuff(&m, x)
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.