X không triển khai Y (Phương thức có một bộ thu con trỏ) [đã đóng]


201

Đã có một số câu hỏi và trả lời về điều này " X không triển khai Y (... phương thức có bộ thu con trỏ) ", nhưng đối với tôi, chúng dường như đang nói về những điều khác nhau và không áp dụng cho trường hợp cụ thể của tôi.

Vì vậy, thay vì làm cho câu hỏi trở nên cụ thể, tôi đang làm cho nó rộng và trừu tượng - Có vẻ như có một vài trường hợp khác nhau có thể gây ra lỗi này, ai đó có thể tóm tắt lại không?

Tức là, làm thế nào để tránh vấn đề, và nếu nó xảy ra, các khả năng là gì? Cám ơn.

Câu trả lời:


365

Lỗi thời gian biên dịch này phát sinh khi bạn cố gắng gán hoặc chuyển (hoặc chuyển đổi) một loại cụ thể thành một loại giao diện; và kiểu chính nó không thực hiện giao diện, chỉ có một con trỏ tới kiểu .

Hãy xem một ví dụ:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

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

Các Stringerloại giao diện có một phương pháp duy nhất: String(). Bất kỳ giá trị nào được lưu trữ trong một giá trị giao diện Stringerđều phải có phương thức này. Chúng tôi cũng đã tạo một MyTypevà chúng tôi đã tạo một phương thức MyType.String()với bộ thu con trỏ . Điều này có nghĩa là String()phương thức nằm trong tập phương thức của *MyTypekiểu, nhưng không phải trong phương thức đó MyType.

Khi chúng ta cố gắng gán giá trị MyTypecho một biến loại Stringer, chúng ta sẽ gặp lỗi trong câu hỏi:

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)

Nhưng mọi thứ đều ổn nếu chúng ta cố gắng gán giá trị của loại *MyTypecho Stringer:

s = &m
fmt.Println(s)

Và chúng tôi nhận được kết quả mong đợi (thử trên Sân chơi Go ):

something

Vì vậy, các yêu cầu để có được lỗi thời gian biên dịch này:

  • Một giá trị của loại bê tông không con trỏ được gán (hoặc được chuyển hoặc chuyển đổi)
  • Một loại giao diện được gán cho (hoặc được chuyển đến hoặc chuyển đổi thành)
  • Loại bê tông có phương thức cần thiết của giao diện, nhưng với một bộ nhận con trỏ

Khả năng giải quyết vấn đề:

  • Phải sử dụng một con trỏ tới giá trị, có tập phương thức sẽ bao gồm phương thức với bộ nhận con trỏ
  • Hoặc loại máy thu phải được thay đổi thành không phải con trỏ , do đó, tập phương thức của loại bê tông không con trỏ cũng sẽ chứa phương thức (và do đó thỏa mãn giao diện). Điều này có thể hoặc không thể tồn tại, vì nếu phương thức phải sửa đổi giá trị, một bộ thu không phải là con trỏ không phải là một tùy chọn.

Cấu tạo và nhúng

Khi sử dụng cấu trúc và nhúng , thường không phải là "bạn" thực hiện giao diện (cung cấp cách thực hiện phương thức), mà là loại bạn nhúng vào struct. Giống như trong ví dụ này:

type MyType2 struct {
    MyType
}

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

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

Một lần nữa, lỗi thời gian biên dịch, vì tập phương thức MyType2không chứa String()phương thức được nhúng MyType, chỉ có tập phương thức *MyType2, do đó, các công việc sau (thử trên Sân chơi Go ):

var s Stringer
s = &m2

Chúng tôi cũng có thể làm cho nó hoạt động, nếu chúng tôi nhúng *MyTypevà chỉ sử dụng một con trỏ không MyType2 (thử nó trên Sân chơi Go ):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = m2

Ngoài ra, bất cứ điều gì chúng tôi nhúng (hoặc MyTypehoặc *MyType), nếu chúng tôi sử dụng một con trỏ *MyType2, nó sẽ luôn hoạt động (thử trên Sân chơi Go ):

type MyType2 struct {
    *MyType
}

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

var s Stringer
s = &m2

Phần có liên quan từ thông số kỹ thuật (từ phần Kiểu cấu trúc ):

Cho một kiểu cấu trúc Svà một kiểu có tên T, các phương thức được thăng cấp được bao gồm trong tập phương thức của cấu trúc như sau:

  • Nếu Schứa một trường ẩn danh T, tập hợp phương thức S*Scả hai bao gồm các phương thức được quảng cáo với người nhận T. Bộ phương thức *Scũng bao gồm các phương thức được quảng bá với máy thu *T.
  • Nếu Schứa một trường ẩn danh *T, bộ phương thức S*Scả hai bao gồm các phương thức được quảng cáo với người nhận Thoặc *T.

Vì vậy, nói cách khác: nếu chúng ta nhúng một loại không phải con trỏ, tập phương thức của bộ nhúng không con trỏ chỉ nhận được các phương thức với các bộ thu không con trỏ (từ loại được nhúng).

Nếu chúng ta nhúng một loại con trỏ, tập hợp phương thức của bộ nhúng không con trỏ sẽ nhận các phương thức với cả hai bộ thu con trỏ và không con trỏ (từ loại được nhúng).

Nếu chúng ta sử dụng một giá trị con trỏ cho trình nhúng, bất kể kiểu nhúng có phải là con trỏ hay không, tập phương thức của con trỏ tới trình nhúng luôn lấy các phương thức có cả con trỏ và con trỏ không con trỏ (từ kiểu nhúng).

Ghi chú:

Có một trường hợp rất giống nhau, cụ thể là khi bạn có một giá trị giao diện bao bọc một giá trị MyTypevà bạn cố gắng nhập xác nhận giá trị giao diện khác từ nó , Stringer. Trong trường hợp này, xác nhận sẽ không giữ vì các lý do được mô tả ở trên, nhưng chúng tôi nhận được một lỗi thời gian chạy hơi khác:

m := MyType{value: "something"}

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

Thời gian chạy hoảng loạn (thử nó trên Sân chơi Go ):

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

Cố gắng chuyển đổi thay vì xác nhận kiểu, chúng tôi nhận được lỗi thời gian biên dịch mà chúng tôi đang nói đến:

m := MyType{value: "something"}

fmt.Println(Stringer(m))

Cảm ơn câu trả lời cực kỳ toàn diện. Xin lỗi vì đã trả lời muộn vì lạ là tôi đã không nhận được thông báo SO. Một trường hợp mà tôi đã tìm kiếm, câu trả lời là "các hàm thành viên" phải là tất cả các loại con trỏ, ví dụ: " func (m *MyType)" hoặc không có . Có phải vậy không Tôi có thể trộn các loại "hàm thành viên" khác nhau không, ví dụ: func (m *MyType)& func (m MyType)?
xpt

3
@xpt Bạn có thể kết hợp các bộ thu con trỏ và không con trỏ, đó không phải là một yêu cầu để làm cho tất cả giống nhau. Thật kỳ lạ nếu bạn có 19 phương thức với bộ thu con trỏ và bạn tạo một phương thức với bộ thu không con trỏ. Điều này cũng khiến việc theo dõi phương thức nào là một phần của tập phương thức của loại nào trở nên khó khăn hơn nếu bạn bắt đầu trộn chúng. Chi tiết hơn trong câu trả lời này: Người nhận giá trị so với người nhận Con trỏ trong Golang?
icza

Làm thế nào để bạn thực sự giải quyết vấn đề được đề cập ở cuối trong "Lưu ý:" với giao diện {} bao bọc một giá trị MyType, nếu bạn không thể thay đổi MyTypeđể sử dụng các phương thức giá trị. Tôi đã thử một cái gì đó như thế này (&i).(*Stringer)nhưng nó không hoạt động. Nó thậm chí có thể?
Joel Edström

1
@ JoelEdström Vâng, điều đó là có thể, nhưng nó rất ít ý nghĩa. Ví dụ, bạn có thể gõ xác nhận giá trị của loại không phải con trỏ và lưu nó trong một biến, ví dụ x := i.(MyType), sau đó bạn có thể gọi các phương thức với bộ nhận con trỏ trên đó, ví dụ i.String(), đó là một tốc ký (&i).String()thành công vì các biến có thể định địa chỉ. Nhưng phương thức con trỏ thay đổi giá trị (giá trị nhọn) sẽ không được phản ánh trong giá trị được bọc trong giá trị giao diện, đó là lý do tại sao nó không có ý nghĩa gì.
icza

1
@DeepNightTwo Các phương thức *Tkhông được bao gồm trong tập phương thức SScó thể không thể truy cập được (ví dụ: giá trị trả về của hàm hoặc kết quả của việc lập chỉ mục bản đồ) và vì thường chỉ có một bản sao được nhận / nhận và nếu lấy địa chỉ của nó được cho phép, phương thức được cho phép với người nhận con trỏ chỉ có thể sửa đổi bản sao (nhầm lẫn như bạn cho rằng bản gốc đã được sửa đổi). Xem câu trả lời này cho một ví dụ: Sử dụng phản chiếu SetString .
icza

33

Để ngắn gọn, giả sử bạn có mã này và bạn có giao diện Trình tải và Trình tải Web thực hiện giao diện này.

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

Vì vậy, mã này sẽ cung cấp cho bạn lỗi thời gian biên dịch này

./main.go:20:13: không thể sử dụng webLoader (loại WebLoader) làm loại Trình tải trong đối số cho loadContent: WebLoader không triển khai Trình tải (Phương thức tải có trình nhận con trỏ)

Vì vậy, những gì bạn chỉ cần làm là thay đổi webLoader := WebLoader{}để làm theo:

webLoader := &WebLoader{} 

Vậy tại sao nó sẽ sửa vì bạn xác định hàm này func (w *WebLoader) Loadđể chấp nhận một bộ nhận con trỏ. Để được giải thích thêm xin vui lòng đọc câu trả lời @icza và @karora


6
Cho đến nay đây là bình luận dễ hiểu nhất. Và trực tiếp giải quyết vấn đề tôi đang gặp phải ..
Maxs728 16/03/19

@ Maxs728 Đồng ý, khá hiếm khi trả lời cho nhiều vấn đề về Go.
milosmns

6

Một trường hợp khác khi tôi thấy loại điều này xảy ra là nếu tôi muốn tạo giao diện trong đó một số phương thức sẽ sửa đổi giá trị nội bộ và các phương thức khác thì không.

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

Một cái gì đó sau đó thực hiện giao diện này có thể giống như:

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
}

Vì vậy, kiểu triển khai có thể sẽ có một số phương thức là máy thu con trỏ và một số phương thức không và vì tôi có khá nhiều thứ khác nhau mà GetterSetters tôi muốn kiểm tra trong các thử nghiệm của mình rằng tất cả chúng đều được mong đợi.

Nếu tôi phải làm một cái gì đó như thế này:

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

Sau đó, tôi sẽ không nhận được lỗi "X không thực hiện Y (phương thức Z có bộ thu con trỏ)" (vì đây là lỗi thời gian biên dịch) nhưng tôi sẽ có một ngày tồi tệ để theo dõi chính xác lý do tại sao thử nghiệm của tôi thất bại .. .

Thay vào đó tôi phải đảm bảo rằng tôi thực hiện kiểm tra loại bằng cách sử dụng một con trỏ, chẳng hạn như:

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

Hoặc là:

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

Sau đó tất cả đều hài lòng với các bài kiểm tra!

Nhưng chờ đã! Trong mã của tôi, có lẽ tôi có các phương thức chấp nhận GetterSetter ở đâu đó:

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

Nếu tôi gọi các phương thức này từ bên trong một phương thức loại khác, điều này sẽ tạo ra lỗi:

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

Một trong những cuộc gọi sau sẽ hoạt động:

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

func (m MyTypeA) OtherThing(x int) {
    SomeStuff(&m, x)
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.