¿Por qué una asignación de interfaz es más estricta que una llamada a un método?

Aug 20 2020

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

3 Marc Aug 20 2020 at 12:32

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 xrefiere a su variable vy 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.. vimplementa Absersolo 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 vcomo lo hizo en la llamada al método. Esto significa que el método configurado para var v Vertexestá vacío y no puede implementar la interfaz.

Solución:

La forma de evitar esto es tomar la dirección de vusted mismo:

var a Abser
v := Vertex{1, 2}

a = &v

Al hacer esto, ahora está viendo el conjunto de métodos *Vertexque incluye Abs() float64y, por lo tanto, implementa la interfaz Abser.