any [disponible desde Go 1.18] es un alias para interface{} y representa un contenedor que puede albergar cualquier tipo de valor. Internamente, el runtime de Go utiliza una estructura llamada eface (empty interface) que almacena dos punteros: uno que apunta al descriptor del tipo de dato y otro que apunta al valor real en memoria. Cuando declaras una variable como any, le estás diciendo al compilador que no sabe qué tipo de dato vendrá, por lo que la seguridad de tipos estática se pierde y la responsabilidad recae totalmente en el desarrollador durante la ejecución.
Este mecanismo es fundamental para procesar datos de fuentes cuyo esquema no conocemos de antemano, como el contenido de un archivo JSON o una respuesta de una API externa. Sin embargo, el uso excesivo de any puede convertir un código robusto en un caos de errores en tiempo de ejecución; si intentas extraer un tipo que no coincide con el valor almacenado sin validar la operación, el programa lanzará un panic y se detendrá.
package main
import (
"fmt"
)
// Command representa un comando interno de nuestro sistema.
type Command struct {
ID int
Payload string
}
// procesarEntrada recibe un valor de tipo any y decide qué hacer
// basándose en el tipo real del dato recibido.
func procesarEntrada(input any) {
// El "Type Switch" es la forma idiomática de manejar múltiples tipos posibles.
// La variable 'v' toma el valor concreto con su tipo correcto dentro de cada case.
switch v := input.(type) {
case int:
fmt.Printf("Procesando entero: %d\n", v)
case string:
fmt.Printf("Procesando cadena: %s\n", v)
case Command:
fmt.Printf("Ejecutando comando ID %d: %s\n", v.ID, v.Payload)
case nil:
fmt.Println("El valor es nil, no hay nada que procesar.")
default:
// Si el tipo no es uno de los conocidos, usamos una "Type Assertion" segura.
// La sintaxis 'v, ok := x.(T)' es vital: si 'ok' es false, 'v' es el zero value.
if f, ok := input.(float64); ok {
fmt.Printf("Tipo inesperado pero es float64: %f\n", f)
} else {
fmt.Printf("Tipo desconocido: %T\n", input)
}
}
}
func main() {
// Simulamos una cola de mensajes con tipos heterogéneos.
mensajes := []any{
42,
"Hola desde el runtime",
Command{ID: 101, Payload: "Reinicio sistema"},
3.14159,
nil,
true, // Este activará el 'default'
}
for _, m := range mensajes {
procesarEntrada(m)
}
}
En el ejemplo anterior, la función procesarEntrada utiliza un switch de tipo (input.(type)). Fíjate cómo dentro del case Command, la variable v deja de ser de tipo any para comportarse como un Command, permitiéndonos acceder a sus campos ID y Payload sin errores de compilación.
Cuando el switch no cubre el tipo que recibimos (como ocurre con el valor 3.14159), recurrimos a la aserción de tipo con la forma v, ok := input.(float64). Esta es la forma segura: si la aserción falla, ok es false y el programa continúa su ejecución sin problemas. Por el contrario, si usáramos la forma directa v := input.(float64) y el valor fuera un string, el programa entraría en panic.
N° 73