Cómo mostrar el tipo de una variable en Rust y no volverse loco
\n\n¿Alguna vez has escrito código en Rust y te has quedado paralizado con la pregunta: "¿Y qué tipo tiene esta variable?" Si es así, no estás solo. La pregunta "How do I print the type of a variable in Rust" en Stack Overflow ha acumulado miles de visitas, y no es casualidad. Rust es un lenguaje con un sistema de tipos estricto pero inteligente, y a veces el compilador sabe más que nosotros.
\n\nEn este artículo, analizaremos tres formas de mostrar el tipo de una variable: desde un simple truco de depuración hasta una macro profesional. Aprenderás cuándo usar cada método y dejarás de preguntarte qué criatura se esconde detrás de let x = ....
¿Por qué Rust no permite simplemente imprimir el tipo?
\n\nEn lenguajes como Python o JavaScript, puedes escribir print(type(x)) y listo. En Rust no existe ese lujo por dos razones:
- \n
- Tipado estático: los tipos se conocen en tiempo de compilación, pero no siempre son obvios para el programador (especialmente al trabajar con closures o genéricos complejos). \n
- Falta de reflexión: Rust no almacena información de tipos en tiempo de ejecución por defecto, ya que afectaría el rendimiento. \n
Pero los desarrolladores de Rust nos han dejado algunos atajos. Veámoslos.
\n\nMétodo 1: std::any::type_name — el más simple (pero no ideal)
\n\nLa biblioteca estándar de Rust proporciona la función std::any::type_name. Devuelve una cadena con el nombre del tipo. Así es como funciona:
use std::any::type_name;\n\nfn print_type<T>(_: &T) {\n println!("{}", type_name::<T>());\n}\n\nfn main() {\n let x = 42;\n let y = "hello";\n let z = vec![1, 2, 3];\n \n print_type(&x); // i32\n print_type(&y); // &str\n print_type(&z); // Vec<i32>\n}\n\nVentajas:
\n- \n
- Mínimo código: funciona de inmediato. \n
- Adecuado para depuración y comprobaciones rápidas. \n
Desventajas:
\n- \n
- El resultado depende del compilador: en diferentes versiones de Rust o en diferentes plataformas, la cadena puede variar. \n
- No se puede usar en lógica de producción donde se necesite tomar decisiones basadas en el tipo. \n
Ejemplo práctico: depuración de una expresión compleja
\n\nImagina que trabajas con iteradores y cadenas de métodos. ¿Qué tipo se obtiene al final?
\n\nuse std::any::type_name;\n\nfn main() {\n let data = vec![1, 2, 3, 4, 5];\n let result = data.iter()\n .filter(|x| *x % 2 == 0)\n .map(|x| x * 2)\n .collect::<Vec<_>>();\n \n // ¿Queremos saber el tipo antes de collect?\n let intermediate = data.iter()\n .filter(|x| *x % 2 == 0)\n .map(|x| x * 2);\n \n println!("Tipo de intermediate: {}", type_name::<decltype!(intermediate)>()); \n // Ups, así no funciona — hay que pasar una referencia\n println!("Tipo de intermediate: {}", type_name_of_val(&intermediate));\n}\n\nfn type_name_of_val<T>(_: &T) -> &str {\n type_name::<T>()\n}\n\nEste truco te salvará cuando el compilador muestre un error y no entiendas qué tipo espera.
\n\nMétodo 2: Usar errores del compilador — un truco astuto
\n\nA veces, la mejor manera de conocer el tipo es obligar al compilador a que lo diga él mismo. Hay un truco antiguo pero efectivo: asignar la variable a un tipo inexistente.
\n\nfn main() {\n let x = 42;\n let y: () = x; // ¡Error! se esperaba (), se encontró i32\n}\n\nEl compilador mostrará algo como:
\nerror[E0308]: mismatched types\n --> src/main.rs:3:18\n |\n3 | let y: () = x;\n | -- ^ expected `()`, found `i32`\n | |\n | expected due to this\n\nVentajas:
\n- \n
- No requiere imports. \n
- Funciona incluso en entornos sin std (no_st