Nhầm lẫn “<type> là con trỏ tới giao diện, không phải giao diện”


102

Kính gửi các nhà phát triển đồng nghiệp,

Tôi gặp vấn đề này có vẻ hơi kỳ lạ đối với tôi. Hãy xem đoạn mã này:

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
}

Trên một số gói khác, tôi có mã sau:

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

Thời gian chạy sẽ không chấp nhận dòng được đề cập bởi vì

"không thể sử dụng fieldfilter (loại * coreinterfaces.FieldFilter) làm loại * coreinterfaces.FilterInterface trong đối số là fieldint.AddFilter: * coreinterfaces.FilterInterface là con trỏ tới giao diện, không phải giao diện"

Tuy nhiên, khi thay đổi mã thành:

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

Mọi thứ đều ổn và khi gỡ lỗi ứng dụng, nó thực sự có vẻ bao gồm

Tôi hơi bối rối về chủ đề này. Khi xem các bài đăng trên blog khác và các luồng tràn ngăn xếp thảo luận về vấn đề tương tự chính xác này (ví dụ - Cái này hoặc Cái này ), đoạn mã đầu tiên nêu ra ngoại lệ này sẽ hoạt động, vì cả bộ lọc trường và bản đồ trường đều được khởi tạo dưới dạng con trỏ đến giao diện, thay vì giá trị của các giao diện. Tôi không thể hiểu những gì thực sự xảy ra ở đây mà tôi cần phải thay đổi để không phải khai báo FieldInterface và chỉ định việc triển khai cho giao diện đó. Phải có một cách thanh lịch để làm điều này.


Khi thay đổi * FilterInterfacethành FilterInterfaceDòng _ = filtermap.AddFilter(fieldfilter)hiện tại nảy sinh điều này: không thể sử dụng bộ lọc trường (loại coreinterfaces.FieldFilter) làm loại coreinterfaces.FilterInterface trong đối số thành filtermap.AddFilter: coreinterfaces.FieldFilter không triển khai coreinterfaces.FilterInterface (Phương thức lọc có bộ thu con trỏ) Tuy nhiên khi thay đổi dòng để _ = filtermap.AddFilter(&fieldfilter)nó hoạt động. chuyện gì xảy ra ở đây thế? tại sao vậy?
0rka

2
Bởi vì các phương thức thực hiện giao diện có bộ thu con trỏ. Truyền một giá trị, nó không triển khai giao diện; chuyển một con trỏ, nó thực hiện, bởi vì các phương pháp sau đó được áp dụng. Nói chung, khi xử lý các giao diện, bạn chuyển một con trỏ đến một cấu trúc đến một hàm mong đợi một giao diện. Bạn hầu như không bao giờ muốn một con trỏ đến một giao diện trong bất kỳ tình huống nào.
Adrian

1
Tôi hiểu quan điểm của bạn, nhưng bằng cách thay đổi giá trị tham số từ * FilterInterfacemột cấu trúc triển khai giao diện này, điều này phá vỡ ý tưởng truyền giao diện cho các hàm. Những gì tôi muốn đạt được không bị ràng buộc với cấu trúc mà tôi đã chuyển, mà là bất kỳ cấu trúc nào triển khai giao diện mà tôi muốn sử dụng. Bất kỳ thay đổi mã nào bạn có thể cho là hiệu quả hơn hoặc đạt tiêu chuẩn để tôi thực hiện? Sẽ rất vui mừng khi sử dụng một số dịch vụ xem xét mã :)
0rka

2
Hàm của bạn nên chấp nhận một đối số giao diện (không phải con trỏ tới giao diện). Người gọi phải chuyển một con trỏ tới một cấu trúc thực thi giao diện. Điều này không "phá vỡ ý tưởng về việc truyền giao diện cho các hàm" - hàm vẫn sử dụng một giao diện, bạn đang truyền một phần cụ thể để triển khai giao diện.
Adrian

Câu trả lời:


138

Vì vậy, bạn đang nhầm lẫn hai khái niệm ở đây. Một con trỏ đến một cấu trúc và một con trỏ đến một giao diện không giống nhau. Một giao diện có thể lưu trữ trực tiếp một cấu trúc hoặc một con trỏ đến một cấu trúc. Trong trường hợp sau, bạn vẫn chỉ sử dụng giao diện trực tiếp, không phải con trỏ đến giao diện. Ví dụ:

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

Đầu ra:

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

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

Trong cả hai trường hợp, fbiến trong DoFoochỉ là một giao diện, không phải là một con trỏ đến một giao diện. Tuy nhiên, khi lưu trữ f2, giao diện giữ một con trỏ đến một Foocấu trúc.

Con trỏ đến các giao diện hầu như không bao giờ hữu ích. Trên thực tế, thời gian chạy Go đặc biệt đã được thay đổi một vài phiên bản để không còn tự động bỏ qua con trỏ giao diện (giống như đối với con trỏ cấu trúc), để ngăn cản việc sử dụng chúng. Trong phần lớn các trường hợp, một con trỏ đến một giao diện phản ánh sự hiểu nhầm về cách các giao diện được cho là hoạt động.

Tuy nhiên, có một hạn chế về giao diện. Nếu bạn truyền trực tiếp một cấu trúc vào một giao diện, thì chỉ các phương thức giá trị của kiểu đó (tức là. func (f Foo) Dummy(), Không func (f *Foo) Dummy()) mới có thể được sử dụng để thực hiện giao diện. Điều này là do bạn đang lưu trữ một bản sao của cấu trúc gốc trong giao diện, vì vậy các phương thức con trỏ sẽ có những tác động không mong muốn (tức là không thể thay đổi cấu trúc ban đầu). Do đó, quy tắc ngón tay cái mặc định là lưu trữ con trỏ đến cấu trúc trong giao diện , trừ khi có lý do thuyết phục để không làm như vậy.

Cụ thể với mã của bạn, nếu bạn thay đổi chữ ký hàm AddFilter thành:

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

Và chữ ký GetFilterByID để:

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

Mã của bạn sẽ hoạt động như mong đợi. fieldfilterlà loại *FieldFilter, điền đầy đủ FilterInterfaceloại giao diện và do đó AddFiltersẽ chấp nhận nó.

Dưới đây là một số tài liệu tham khảo tốt để hiểu cách các phương thức, kiểu và giao diện hoạt động và tích hợp với nhau trong Go:


"Điều này là do bạn đang lưu trữ một bản sao của cấu trúc ban đầu trong giao diện, vì vậy các phương thức con trỏ sẽ có những tác động không mong muốn (tức là không thể thay đổi cấu trúc ban đầu)" - đây không phải là lý do giải thích cho giới hạn. Rốt cuộc, bản sao duy nhất có thể đã được lưu trữ trong giao diện.
WPWoodJr

Câu trả lời của bạn ở đó không có ý nghĩa. Bạn đang giả định rằng vị trí mà kiểu cụ thể được lưu trữ trong giao diện không thay đổi khi bạn thay đổi những gì được lưu trữ ở đó, điều này không xảy ra và điều đó sẽ hiển nhiên nếu bạn đang lưu trữ thứ gì đó với bố cục bộ nhớ khác. Những gì bạn không nhận được về nhận xét con trỏ của tôi là một phương pháp nhận con trỏ trên một kiểu cụ thể luôn có thể sửa đổi bộ thu mà nó đang được gọi. Một giá trị được lưu trữ trong một giao diện buộc một bản sao mà sau đó bạn không thể nhận được tham chiếu đến, vì vậy các bộ thu con trỏ không thể sửa đổi dấu chấm, bản gốc.
Kaedys 20/02/18

5
GetFilterByID(i uuid.UUID) *FilterInterface

Khi tôi gặp lỗi này, thường là do tôi đang chỉ định một con trỏ đến một giao diện thay vì một giao diện (thực sự đó sẽ là một con trỏ đến cấu trúc của tôi đáp ứng giao diện).

Có một cách sử dụng hợp lệ cho * interface {...} nhưng phổ biến hơn là tôi chỉ nghĩ 'đây là một con trỏ' thay vì 'đây là một giao diện tình cờ là một con trỏ trong mã tôi đang viết'

Chỉ ném nó ra khỏi đó vì câu trả lời được chấp nhận, mặc dù chi tiết, không giúp tôi gỡ rối.

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.