¿Por qué una asignación de interfaz es más estricta que una llamada a un método?
Estoy pasando por el Tour of Go. Aprendí que si tenemos un método que acepta un puntero como receptor, también aceptará un tipo de valor como receptor (con ir haciendo la conversión automáticamente).
type Vertex struct { X, Y float64 }
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X * v.X + v.Y * v.Y)
}
Entonces el siguiente código es válido, ya sea que Vertex se reciba por valor o puntero
v := Vertex{1, 2}
fmt.Println(v.Abs())
p := &v
fmt.Println(p.Abs())
Sin embargo, digamos que tenemos la siguiente interfaz:
type Abser interface {
Abs() float64
}
Entonces, ¿por qué el siguiente código no es válido?
var a Abser
v := Vertex{1, 2}
a = v // invalid
Tenía entendido que esto estaría bien. Aunque v es un tipo de valor que "implementa" la función Abs que toma un receptor de puntero, ¿también lo tomaría por valor?
¿Se diseñaron las interfaces simplemente para ser más estrictas en términos de lo que una variable de interfaz puede contener en el lado derecho? La interfaz ve * Vertex y Vertex como dos tipos diferentes, sin embargo, el método Abs () tampoco tiene problemas de procesamiento.
Respuestas
En ambos casos, Go quiere un puntero para ejecutar el método. La gran diferencia es que las llamadas a métodos tomarán automáticamente la dirección de v
, pero comprobar si algo implementa una interfaz no lo hace.
Llamadas a métodos:
Al llamar a un método con un receptor de puntero en un tipo plano, Go tomará la dirección automáticamente si está permitido. De la especificación sobre llamadas a métodos (el resaltado es mío):
Una llamada al método xm () es válida si el conjunto de métodos de (el tipo de) x contiene my la lista de argumentos se puede asignar a la lista de parámetros de m. Si x es direccionable y el conjunto de métodos de & x contiene m, xm () es la abreviatura de (& x) .m ()
En este caso, se x
refiere a su variable v
y es direccionable . Entonces, en las llamadas a métodos, Go se ejecuta automáticamente (&v).Abs()
.
Asignación:
Al intentar asignar a = v
, el cheque que se debe completar es T is an interface type and x implements T.
. v
implementa Abser
solo si su conjunto de métodos coincide con la interfaz. Este conjunto de métodos se determina de la siguiente manera:
El conjunto método de cualquier otro tipo T se compone de todos los métodos declarados con el tipo de receptor T . El conjunto de métodos del tipo de puntero correspondiente * T es el conjunto de todos los métodos declarados con el receptor * T o T (es decir, también contiene el conjunto de métodos de T).
Notará que al calcular el conjunto de métodos, Go no toma la dirección de v
como lo hizo en la llamada al método. Esto significa que el método configurado para var v Vertex
está vacío y no puede implementar la interfaz.
Solución:
La forma de evitar esto es tomar la dirección de v
usted mismo:
var a Abser
v := Vertex{1, 2}
a = &v
Al hacer esto, ahora está viendo el conjunto de métodos *Vertex
que incluye Abs() float64
y, por lo tanto, implementa la interfaz Abser
.