Confusión de "<tipo> es un puntero a la interfaz, no a la interfaz"

104

Estimados compañeros desarrolladores,

Tengo este problema que me parece un poco extraño. Eche un vistazo a este fragmento 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
}

En algún otro paquete, tengo el siguiente código:

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

El tiempo de ejecución no aceptará la línea mencionada porque

"no se puede usar fieldfilter (tipo * coreinterfaces.FieldFilter) como tipo * coreinterfaces.FilterInterface en el argumento de fieldint.AddFilter: * coreinterfaces.FilterInterface es un puntero a la interfaz, no a la interfaz"

Sin embargo, al cambiar el código a:

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

Todo está bien y al depurar la aplicación realmente parece incluir

Estoy un poco confundido sobre este tema. Al mirar otras publicaciones de blog y los subprocesos de desbordamiento de pila que discuten exactamente este mismo problema (por ejemplo, esto o esto ), el primer fragmento que genera esta excepción debería funcionar, porque tanto el filtro de campo como el mapa de campo se inicializan como punteros a interfaces, en lugar del valor de interfaces. No he podido entender lo que realmente sucede aquí que necesito cambiar para no declarar una FieldInterface y asignar la implementación para esa interfaz. Debe haber una forma elegante de hacer esto.

0rka
fuente
Cuando se cambia * FilterInterfacea FilterInterfaceLa línea _ = filtermap.AddFilter(fieldfilter)ahora surge esto: no se puede usar fieldfilter (tipo coreinterfaces.FieldFilter) como tipo coreinterfaces.FilterInterface en argumento para filtermap.AddFilter: coreinterfaces.FieldFilter no implementa coreinterfaces.FilterInterface (El método de filtro tiene puntero receptor) Sin embargo, al cambiar el línea a _ = filtermap.AddFilter(&fieldfilter)que funciona. ¿Qué pasa aquí? ¿porqué es eso?
0rka
2
Porque los métodos que implementan la interfaz tienen receptores de puntero. Pasando un valor, no implementa la interfaz; pasando un puntero, lo hace, porque los métodos se aplican. En términos generales, cuando se trata de interfaces, pasa un puntero a una estructura a una función que espera una interfaz. Casi nunca desea un puntero a una interfaz en ningún escenario.
Adrian
1
Entiendo su punto, pero al cambiar el valor del parámetro de * FilterInterfacea una estructura que implementa esta interfaz, se rompe la idea de pasar interfaces a funciones. Lo que quería lograr no es estar vinculado a la estructura que estaba pasando, sino a cualquier estructura que implemente la interfaz que me interesa usar. ¿Algún cambio de código que pueda pensar que es más eficiente o que cumple con los estándares para mí? Estaré encantado de utilizar algunos servicios de revisión de código :)
0rka
2
Su función debe aceptar un argumento de interfaz (no un puntero a la interfaz). La persona que llama debe pasar un puntero a una estructura que implementa la interfaz. Esto no "rompe la idea de pasar interfaces a funciones" - la función todavía toma una interfaz, estás pasando una concreción que implementa la interfaz.
Adrian

Respuestas:

140

Entonces estás confundiendo dos conceptos aquí. Un puntero a una estructura y un puntero a una interfaz no son lo mismo. Una interfaz puede almacenar una estructura directamente o un puntero a una estructura. En el último caso, todavía usa la interfaz directamente, no un puntero a la interfaz. Por ejemplo:

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

Salida:

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

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

En ambos casos, la fvariable in DoFooes solo una interfaz, no un puntero a una interfaz. Sin embargo, al almacenar f2, la interfaz tiene un puntero a una Fooestructura.

Los punteros a las interfaces casi nunca son útiles. De hecho, el tiempo de ejecución de Go se cambió específicamente en algunas versiones para que ya no desreferenciaran automáticamente los punteros de interfaz (como lo hace con los punteros de estructura), para desalentar su uso. En la inmensa mayoría de los casos, un puntero a una interfaz refleja un malentendido de cómo se supone que funcionan las interfaces.

Sin embargo, existe una limitación en las interfaces. Si pasa una estructura directamente a una interfaz, solo se pueden usar métodos de valor de ese tipo (es decir func (f Foo) Dummy(), no func (f *Foo) Dummy()) para completar la interfaz. Esto se debe a que está almacenando una copia de la estructura original en la interfaz, por lo que los métodos de puntero tendrían efectos inesperados (es decir, no pueden alterar la estructura original). Por lo tanto, la regla general predeterminada es almacenar punteros a estructuras en interfaces , a menos que haya una razón convincente para no hacerlo.

Específicamente con su código, si cambia la firma de la función AddFilter a:

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

Y la firma GetFilterByID para:

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

Tu código funcionará como se esperaba. fieldfilteres de tipo *FieldFilter, que completa el FilterInterfacetipo de interfaz y, por AddFilterlo tanto, lo aceptará.

Aquí hay un par de buenas referencias para comprender cómo los métodos, tipos e interfaces funcionan y se integran entre sí en Go:

Kaedys
fuente
"Esto se debe a que está almacenando una copia de la estructura original en la interfaz, por lo que los métodos de puntero tendrían efectos inesperados (es decir, no pueden alterar la estructura original)"; esto no tiene sentido como motivo de la limitación. Después de todo, es posible que la única copia se haya almacenado en la interfaz desde el principio.
WPWoodJr
Tu respuesta no tiene sentido. Está asumiendo que la ubicación en la que el tipo concreto almacenado en la interfaz no cambia cuando cambia lo que está almacenado allí, lo cual no es el caso, y eso debería ser obvio si está almacenando algo con un diseño de memoria diferente. Lo que no entiendes sobre mi comentario de puntero es que un método de receptor de puntero en un tipo concreto siempre puede modificar el receptor al que se llama. Un valor almacenado en una interfaz fuerza una copia a la que luego no puede obtener una referencia, por lo que los receptores de puntero no pueden modificar el punto original.
Kaedys
5
GetFilterByID(i uuid.UUID) *FilterInterface

Cuando recibo este error, generalmente es porque estoy especificando un puntero a una interfaz en lugar de una interfaz (que en realidad será un puntero a mi estructura que cumple con la interfaz).

Hay un uso válido para * interface {...} pero lo más común es que pienso 'esto es un puntero' en lugar de 'esta es una interfaz que resulta ser un puntero en el código que estoy escribiendo'

Simplemente tirándolo porque la respuesta aceptada, aunque detallada, no me ayudó a solucionar el problema.

Daniel Farrell
fuente