“<tipo> é uma confusão de ponteiro para interface, não interface”

104

Caros colegas desenvolvedores,

Estou com um problema que me parece um pouco estranho. Dê uma olhada neste snippet de código:

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
}

Em algum outro pacote, tenho o seguinte código:

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

O tempo de execução não aceitará a linha mencionada porque

"não pode usar fieldfilter (tipo * coreinterfaces.FieldFilter) como tipo * coreinterfaces.FilterInterface no argumento para fieldint.AddFilter: * coreinterfaces.FilterInterface é um ponteiro para interface, não interface"

No entanto, ao alterar o código para:

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

Tudo está bem e ao depurar o aplicativo parece realmente incluir

Estou um pouco confuso sobre este assunto. Ao olhar para outras postagens de blog e tópicos de estouro de pilha discutindo exatamente o mesmo problema (por exemplo - This ou This ), o primeiro snippet que levanta essa exceção deve funcionar, porque fieldfilter e fieldmap são inicializados como ponteiros para interfaces, em vez de valor de interfaces. Não consegui entender o que realmente acontece aqui e que preciso mudar para não declarar um FieldInterface e atribuir a implementação para essa interface. Deve haver uma maneira elegante de fazer isso.

0rka
fonte
Ao mudar * FilterInterfacepara FilterInterfaceA linha _ = filtermap.AddFilter(fieldfilter)agora aumenta isso: não é possível usar fieldfilter (digite coreinterfaces.FieldFilter) como tipo coreinterfaces.FilterInterface no argumento para filtermap.AddFilter: coreinterfaces.FieldFilter não implementa coreinterfaces.FilterInterface (método de filtro tem receptor de ponteiro) No entanto, ao alterar o linha para _ = filtermap.AddFilter(&fieldfilter)ele funciona. o que acontece aqui? por que é que?
0rka
2
Porque os métodos que implementam a interface têm receptores de ponteiro. Passando um valor, não implementa a interface; passando um ponteiro, ele faz, porque os métodos então se aplicam. De modo geral, ao lidar com interfaces, você passa um ponteiro para uma estrutura para uma função que espera uma interface. Você quase nunca quer um ponteiro para uma interface em qualquer cenário.
Adrian
1
Eu entendo seu ponto, mas ao alterar o valor do parâmetro de * FilterInterfacepara uma estrutura que implementa essa interface, isso quebra a ideia de passar interfaces para funções. O que eu queria realizar não era estar vinculado à estrutura que estava passando, mas sim qualquer estrutura que implemente a interface que estou interessado em usar. Alguma alteração de código que você possa considerar mais eficiente ou dentro dos padrões para eu fazer? Terei prazer em usar alguns serviços de revisão de código :)
0rka
2
Sua função deve aceitar um argumento de interface (não um ponteiro para interface). O chamador deve passar um ponteiro para uma estrutura que implementa a interface. Isso não "quebra a ideia de passar interfaces para funções" - a função ainda leva uma interface, você está passando em uma concretização que implementa a interface.
Adrian

Respostas:

140

Então você está confundindo dois conceitos aqui. Um ponteiro para uma estrutura e um ponteiro para uma interface não são iguais. Uma interface pode armazenar uma estrutura diretamente ou um ponteiro para uma estrutura. No último caso, você ainda usa a interface diretamente, não um ponteiro para a interface. Por exemplo:

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

Resultado:

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

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

Em ambos os casos, a fvariável em DoFooé apenas uma interface, não um ponteiro para uma interface. No entanto, ao armazenar f2, a interface mantém um ponteiro para uma Fooestrutura.

Ponteiros para interfaces quase nunca são úteis. Na verdade, o tempo de execução Go foi alterado especificamente algumas versões de volta para não mais desreferenciar ponteiros de interface automaticamente (como faz para ponteiros de estrutura), para desencorajar seu uso. Na esmagadora maioria dos casos, um ponteiro para uma interface reflete um mal-entendido sobre como as interfaces deveriam funcionar.

No entanto, há uma limitação nas interfaces. Se você passar uma estrutura diretamente para uma interface, apenas os métodos de valor desse tipo (ou seja,func (f Foo) Dummy() , não func (f *Foo) Dummy()) podem ser usados ​​para preencher a interface. Isso ocorre porque você está armazenando uma cópia da estrutura original na interface, de modo que os métodos de ponteiro teriam efeitos inesperados (ou seja, incapaz de alterar a estrutura original). Portanto, a regra padrão é armazenar ponteiros para estruturas em interfaces , a menos que haja uma razão convincente para não fazê-lo.

Especificamente com seu código, se você alterar a assinatura da função AddFilter para:

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

E a assinatura GetFilterByID para:

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

Seu código funcionará conforme o esperado. fieldfilteré do tipo*FieldFilter , que preenche o FilterInterfacetipo de interface e, portanto AddFilter, o aceita.

Aqui estão algumas boas referências para entender como métodos, tipos e interfaces funcionam e se integram no Go:

Kaedys
fonte
"Isso ocorre porque você está armazenando uma cópia da estrutura original na interface, portanto, os métodos de ponteiro teriam efeitos inesperados (ou seja, incapaz de alterar a estrutura original)" - isso não faz sentido como o motivo da limitação. Afinal, a única cópia pode ter sido armazenada na interface o tempo todo.
WPWoodJr
Sua resposta aí não faz sentido. Você está assumindo que a localização na qual o tipo concreto armazenado na interface não muda quando você altera o que está armazenado lá, o que não é o caso, e isso deve ser óbvio se você estiver armazenando algo com um layout de memória diferente. O que você não está entendendo sobre meu comentário de ponteiro é que um método de receptor de ponteiro em um tipo concreto sempre pode modificar o receptor em que está sendo chamado. Um valor armazenado em uma interface força uma cópia da qual você não pode obter uma referência, portanto, os receptores de ponteiro não podem modificar o ponto original.
Kaedys de
5
GetFilterByID(i uuid.UUID) *FilterInterface

Quando recebo esse erro, geralmente é porque estou especificando um ponteiro para uma interface em vez de uma interface (que na verdade será um ponteiro para minha estrutura que preenche a interface).

Há um uso válido para * interface {...} mas, mais comumente, estou pensando 'isto é um ponteiro' em vez de 'esta é uma interface que por acaso é um ponteiro no código que estou escrevendo'

Só estou lançando porque a resposta aceita, embora detalhada, não me ajudou a resolver o problema.

Daniel Farrell
fonte