> serie: aprende go de 0 a 100 | artículo 7 de 19
En el artículo anterior las interfaces eran elegancia pura. Hoy visitamos el sótano de las type assertions en Go: qué pasa cuando necesitas recuperar el tipo concreto que vive dentro de una interfaz, qué es ese any que aparece por todas partes y el gotcha del nil que ha mordido a todo gopher al menos una vez.
La interfaz vacía y any
Una interfaz sin métodos, interface{}, no exige nada. Por tanto TODOS los tipos la satisfacen. Desde Go 1.18, any es su alias oficial (notas de la versión 1.18) y es la forma recomendada de escribirlo.
Es el comodín que usa fmt.Println(a …any) para aceptar lo que sea. Pero ojo: meter un valor en un any es fácil, lo difícil es sacarlo. El compilador ya no sabe qué hay dentro, y todo lo que ganaste con el tipado estático lo pagas a la salida. Úsalo en las fronteras (JSON, logging, impresión), no como atajo para no pensar los tipos.
Type assertion: abrir la caja
Para recuperar el tipo concreto de una interfaz existe la type assertion (A Tour of Go y especificación):
var i any = "hola"
// Forma peligrosa: si no es string, panic.
s := i.(string)
// Forma segura: la coma ok.
s, ok := i.(string)
if !ok {
fmt.Println("no era un string")
}
La forma con coma ok nunca hace panic: devuelve el zero value y false si el tipo no coincide. Es la misma coma ok que ya viste en maps y channels. En la práctica: usa siempre la forma segura salvo que tengas garantía absoluta del tipo.
Type switch: preguntar por tipos en cadena
Cuando hay más de dos tipos posibles, encadenar type assertions en Go es un dolor. Para eso está el type switch (A Tour of Go y especificación):
func describe(x any) string {
switch v := x.(type) {
case int:
return fmt.Sprintf("entero: %d", v)
case string:
return fmt.Sprintf("string de %d bytes", len(v))
case []int:
return fmt.Sprintf("slice con %d elementos", len(v))
case nil:
return "un nil como una casa"
default:
return fmt.Sprintf("ni idea: %T", v)
}
}
La sintaxis x.(type) solo vale dentro de un switch. En cada rama, v ya tiene el tipo concreto del case: dentro de case int es un int de pleno derecho, sin conversiones. Si vienes de Java, esto es el instanceof con pattern matching, pero de 2009. En Python sería una cadena de isinstance.

El gotcha del nil que no es nil
Prepárate, que viene el susto favorito de las entrevistas de Go:
type MiError struct{}
func (e *MiError) Error() string { return "boom" }
func falla() error {
var p *MiError = nil
return p // ¡aquí está la trampa!
}
err := falla()
fmt.Println(err == nil) // false. Sí, false.
¿Cómo puede err == nil dar false si devolvimos un puntero nil? Porque una interfaz guarda dos cosas: el tipo y el valor. Aquí el valor es nil, pero el tipo es *MiError, así que la interfaz NO es nil. Una interfaz solo es nil cuando tipo y valor son ambos nil (FAQ oficial, Effective Go).
La moraleja oficial: las funciones que devuelven errores deben declarar error como tipo de retorno y devolver nil literal, nunca un puntero concreto que pueda ser nil. Guárdate esta, que en el artículo 9 volvemos sobre ella.
Reto de la semana: pon a prueba las type assertions en Go
- Escribe describe(x any) string con un type switch que distinga al menos cinco tipos, y pruébala con valores variados.
- Reproduce el gotcha del nil: copia el código de falla(), verifica el false y arréglalo devolviendo nil explícito.
- Bonus: dado un []any con tipos mezclados, suma solo los números (int y float64) usando type assertions con coma ok.
Type assertions en Go: nivel completado.
En el próximo artículo: slices, arrays y maps por dentro. Punteros escondidos, capacidades que crecen y el motivo de los índices raros del reto del artículo 3.
> exit