DMarlon Studio

Lección 2 · 2 de 20

Interfaz vacía y switch de tipos · Go

Guarde valores de cualquier tipo con any (interface{}) en Go y recupere el tipo concreto con aserciones de tipo y switch de tipos.

Go · Interfaz vacía y switch de tipos en DMarlon Studio: el código del ejemplo y su salida en la terminal

La interfaz vacía interface{} no pide ningún método, así que todos los tipos la cumplen: una variable de ese tipo puede guardar cualquier valor. Desde Go 1.18 tiene un nombre más corto, `any`. values es un []any con un entero, un decimal, un texto, una lista, un booleano y nil, como los ajustes que llegan de un fichero de configuración.

Un any guarda el valor y su tipo, pero para trabajar con él hay que recuperar el tipo concreto. El switch de tipos switch v := v.(type) lo hace: dentro de cada case, v ya es de ese tipo, y por eso en case string vale len(v) y en case []string se pueden contar los elementos. case nil atrapa el valor ausente y default, todo lo demás; %T escribe el nombre del tipo (bool).

Para un solo tipo está la aserción de tipo. Con dos resultados, n, ok := port.(int), no hay riesgo: port guarda el texto "8080", no un entero, así que ok vale false y n se queda en el valor cero, 0.

Con un solo resultado, values[0].(int) devuelve el entero directamente (8080 * 2 = 16160), pero si el tipo no coincidiera provocaría un panic y el programa se detendría. Úsela solo cuando esté seguro del tipo; si hay dudas, use ok o un switch.

any es útil en los bordes del programa, como un JSON sin estructura fija o el propio fmt.Println, pero con él se pierde la comprobación de tipos del compilador. Dentro de su código, prefiera tipos concretos, interfaces con métodos o genéricos.

Código · main.go

package mainimport "fmt"// describe uses a type switch: in each case, v already has that concrete type.func describe(v interface{}) string {	switch v := v.(type) {	case nil:		return "nil: the setting is missing"	case int:		return fmt.Sprintf("int %d", v)	case float64:		return fmt.Sprintf("float64 %.2f", v)	case string:		return fmt.Sprintf("string %q (%d bytes)", v, len(v))	case []string:		return fmt.Sprintf("[]string with %d items: %v", len(v), v)	default:		return fmt.Sprintf("something else: %T", v)	}}func main() {	// any is another name for interface{}: it can hold a value of any type.	values := []any{8080, 0.75, "shop.local", []string{"api", "eu"}, true, nil}	for _, v := range values {		fmt.Println(describe(v))	}	var port any = "8080"	n, ok := port.(int) // comma-ok form: never panics	fmt.Println("port as int:", n, ok)	fmt.Println("first value doubled:", values[0].(int)*2) // panics if wrong}

Salida · go run main.go

int 8080
float64 0.75
string "shop.local" (10 bytes)
[]string with 2 items: [api eu]
something else: bool
nil: the setting is missing
port as int: 0 false
first value doubled: 16160

Para recordar

  • any es lo mismo que interface{}: admite cualquier valor.
  • switch v := x.(type) da a v el tipo de cada case.
  • n, ok := x.(int) nunca falla: ok dice si el tipo coincide.
  • x.(int) sin ok provoca un panic si el tipo no es el esperado.
  • Use any en los bordes; dentro, tipos concretos.

Ejercicio

Añada a describe un case bool que escriba flag on o flag off, y cambie la última línea por values[2].(int) para ver el panic que produce una aserción equivocada.