go

Explicar las aserciones de tipo en Go



(3)

Respuesta corta

En una línea:

x.(T) afirma que x no es nulo y que el valor almacenado en x es de tipo T

¿Por qué los usaría?

  • para comprobar x es nulo
  • para verificar si es convertible (afirmar) a otro tipo
  • convertir (afirmar) a otro tipo

Qué devuelven exactamente:

  • t := x.(T) => t es de tipo T ; si x es nulo, entra en pánico.

  • t,ok := x.(T) => si x es nulo o no del tipo T => ok es false contrario, ok es true y t es del tipo T

Explicación detallada

Imagine que necesita calcular el área de 4 formas diferentes: Círculo, Cuadrado, Rectángulo y Triángulo. Puede definir nuevos tipos con un nuevo método llamado Area() , como este:

type Circle struct { Radius float64 } func (t Circle) Area() float64 { return math.Pi * t.Radius * t.Radius }

Y para Triangle :

type Triangle struct { A, B, C float64 // lengths of the sides of a triangle. } func (t Triangle) Area() float64 { p := (t.A + t.B + t.C) / 2.0 // perimeter half return math.Sqrt(p * (p - t.A) * (p - t.B) * (p - t.C)) }

Y para Rectangle :

type Rectangle struct { A, B float64 } func (t Rectangle) Area() float64 { return t.A * t.B }

Y para Square :

type Square struct { A float64 } func (t Square) Area() float64 { return t.A * t.A }

Aquí tienes Circle , con radio de 1.0, y otras formas con sus lados:

shapes := []Shape{ Circle{1.0}, Square{1.772453}, Rectangle{5, 10}, Triangle{10, 4, 7}, }

¡Interesante! ¿Cómo podemos recogerlos todos en un solo lugar?
Primero necesita la Shape interface para recopilarlos todos en una rebanada de forma []Shape :

type Shape interface { Area() float64 }

Ahora puedes recogerlos así:

shapes := []Shape{ Circle{1.0}, Square{1.772453}, Rectangle{5, 10}, Triangle{10, 4, 7}, }

Después de todo, Circle es una Shape y Triangle es una Shape .
Ahora puede imprimir el área de cada forma usando la declaración individual v.Area() :

for _, v := range shapes { fmt.Println(v, "/tArea:", v.Area()) }

Entonces Area() es una interfaz común entre todas las formas. Ahora, ¿cómo podemos calcular y llamar a un método poco común como ángulos de triángulo usando las shapes anteriores?

func (t Triangle) Angles() []float64 { return []float64{angle(t.B, t.C, t.A), angle(t.A, t.C, t.B), angle(t.A, t.B, t.C)} } func angle(a, b, c float64) float64 { return math.Acos((a*a+b*b-c*c)/(2*a*b)) * 180.0 / math.Pi }

Ahora es el momento de extraer el Triangle de las shapes anteriores:

for _, v := range shapes { fmt.Println(v, "/tArea:", v.Area()) if t, ok := v.(Triangle); ok { fmt.Println("Angles:", t.Angles()) } }

Usando t, ok := v.(Triangle) solicitamos aserciones de tipo, lo que significa que le pedimos al compilador que intente convertir v de tipo Shape para escribir Triangle , de modo que si tiene éxito, el ok será true contrario false , y luego si es exitoso llamar a t.Angles() para calcular los tres ángulos del triángulo.

Esta es la salida:

Circle (Radius: 1) Area: 3.141592653589793 Square (Sides: 1.772453) Area: 3.1415896372090004 Rectangle (Sides: 5, 10) Area: 50 Triangle (Sides: 10, 4, 7) Area: 10.928746497197197 Angles: [128.68218745348943 18.194872338766785 33.12294020774379]

Y todo el código de muestra de trabajo:

package main import "fmt" import "math" func main() { shapes := []Shape{ Circle{1.0}, Square{1.772453}, Rectangle{5, 10}, Triangle{10, 4, 7}, } for _, v := range shapes { fmt.Println(v, "/tArea:", v.Area()) if t, ok := v.(Triangle); ok { fmt.Println("Angles:", t.Angles()) } } } type Shape interface { Area() float64 } type Circle struct { Radius float64 } type Triangle struct { A, B, C float64 // lengths of the sides of a triangle. } type Rectangle struct { A, B float64 } type Square struct { A float64 } func (t Circle) Area() float64 { return math.Pi * t.Radius * t.Radius } // Heron''s Formula for the area of a triangle func (t Triangle) Area() float64 { p := (t.A + t.B + t.C) / 2.0 // perimeter half return math.Sqrt(p * (p - t.A) * (p - t.B) * (p - t.C)) } func (t Rectangle) Area() float64 { return t.A * t.B } func (t Square) Area() float64 { return t.A * t.A } func (t Circle) String() string { return fmt.Sprint("Circle (Radius: ", t.Radius, ")") } func (t Triangle) String() string { return fmt.Sprint("Triangle (Sides: ", t.A, ", ", t.B, ", ", t.C, ")") } func (t Rectangle) String() string { return fmt.Sprint("Rectangle (Sides: ", t.A, ", ", t.B, ")") } func (t Square) String() string { return fmt.Sprint("Square (Sides: ", t.A, ")") } func (t Triangle) Angles() []float64 { return []float64{angle(t.B, t.C, t.A), angle(t.A, t.C, t.B), angle(t.A, t.B, t.C)} } func angle(a, b, c float64) float64 { return math.Acos((a*a+b*b-c*c)/(2*a*b)) * 180.0 / math.Pi }

Ver también:

Aserciones de tipo

Para una expresión x de tipo de interfaz y un tipo T, la expresión primaria

x.(T)

afirma que x no es nulo y que el valor almacenado en x es de tipo T. La notación x. (T) se denomina aserción de tipo.

Más precisamente, si T no es un tipo de interfaz, x. (T) afirma que el tipo dinámico de x es idéntico al tipo T. En este caso, T debe implementar el tipo (interfaz) de x; de lo contrario, la aserción de tipo no es válida, ya que no es posible que x almacene un valor de tipo T. Si T es un tipo de interfaz, x. (T) afirma que el tipo dinámico de x implementa la interfaz T.

Si se mantiene la aserción de tipo, el valor de la expresión es el valor almacenado en x y su tipo es T. Si la aserción de tipo es falsa, se produce un pánico en tiempo de ejecución. En otras palabras, aunque el tipo dinámico de x solo se conoce en tiempo de ejecución, se sabe que el tipo de x. (T) es T en un programa correcto.

var x interface{} = 7 // x has dynamic type int and value 7 i := x.(int) // i has type int and value 7 type I interface { m() } var y I s := y.(string) // illegal: string does not implement I (missing method m) r := y.(io.Reader) // r has type io.Reader and y must implement both I and io.Reader

Una aserción de tipo utilizada en una asignación o inicialización de la forma especial

v, ok = x.(T) v, ok := x.(T) var v, ok = x.(T)

produce un valor booleano sin tipo adicional. El valor de ok es verdadero si la afirmación se cumple. De lo contrario, es falso y el valor de v es el valor cero para el tipo T. No se produce pánico en tiempo de ejecución en este caso .

EDITAR

Pregunta : ¿Qué devuelve la aserción x.(T) cuando T es una interface{} y no un tipo concreto?
Respuesta :

Afirma que x no es nulo y que el valor almacenado en x es de tipo T.

Por ejemplo, esto entra en pánico (compilación: éxito, ejecución: panic: interface conversion: interface is nil, not interface {} ):

package main func main() { var i interface{} // nil var _ = i.(interface{}) }

Y esto funciona (Ejecutar: OK):

package main import "fmt" func main() { var i interface{} // nil b, ok := i.(interface{}) fmt.Println(b, ok) // <nil> false i = 2 c, ok := i.(interface{}) fmt.Println(c, ok) // 2 true //var j int = c // cannot use c (type interface {}) as type int in assignment: need type assertion //fmt.Println(j) }

Salida:

<nil> false 2 true

NOTA: aquí c es de tipo interface {} y no int .


Vea este código de muestra de trabajo con salidas comentadas:

package main import "fmt" func main() { const fm = "''%T''/t''%#[1]v''/t''%[1]v''/t%v/n" var i interface{} b, ok := i.(interface{}) fmt.Printf(fm, b, ok) // ''<nil>'' ''<nil>'' ''<nil>'' false i = 2 b, ok = i.(interface{}) fmt.Printf(fm, b, ok) // ''int'' ''2'' ''2'' true i = "Hi" b, ok = i.(interface{}) fmt.Printf(fm, b, ok) // ''string'' ''"Hi"'' ''Hi'' true i = new(interface{}) b, ok = i.(interface{}) fmt.Printf(fm, b, ok) // ''*interface {}'' ''(*interface {})(0xc042004330)'' ''0xc042004330'' true i = struct{}{} b, ok = i.(interface{}) fmt.Printf(fm, b, ok) // ''struct {}'' ''struct {}{}'' ''{}'' true i = fmt.Println b, ok = i.(interface{}) fmt.Printf(fm, b, ok) // ''func(...interface {}) (int, error)'' ''(func(...interface {}) (int, error))(0x456740)'' ''0x456740'' true i = Shape.Area b, ok = i.(interface{}) fmt.Printf(fm, b, ok) // ''func(main.Shape) float64'' ''(func(main.Shape) float64)(0x401910)'' ''0x401910'' true } type Shape interface { Area() float64 }

Estoy leyendo sobre las aserciones de tipo x.(T) en The Go Programming Language y no las entiendo.

Entiendo que hay diferentes escenarios:

  • T es un tipo concreto o una interfaz
  • Se pueden devolver uno (¿valor afirmado?) O dos (ok) valores

Esto es lo que no entiendo:

  • ¿Por qué los usaría?
  • ¿Qué regresan exactamente?

También busqué en Google el tema y todavía no lo entiendo.


Caso de uso común: verifique si el error devuelto es de tipo T.

https://golang.org/ref/spec#Type_assertions

Para una sola afirmación de valor de retorno: cuando falla, el programa entra en pánico.

Para una afirmación de dos valores de retorno: cuando falla, el segundo argumento se establece en falso y el programa no entra en pánico.


Una aserción de tipo es la notación x. (T) donde x es del tipo de interfaz y T es un tipo. Además, el valor real almacenado en x es de tipo T, y T debe satisfacer el tipo de interfaz de x.