Introducción: ¿por qué Rust sabe hacer magia con el código?
\n\n¿Alguna vez has escrito en Rust y has sentido que repites las mismas construcciones para una docena de estructuras? ¿O te gustaría generar automáticamente la implementación de un trait sin copiar el código manualmente? Bienvenido al mundo de la metaprogramación en Rust — técnicas que permiten escribir código que escribe código.
\n\nRust no tiene una reflexión completa en tiempo de ejecución (como Java o Python), pero ofrece algo más potente y seguro — macros. Funcionan en la etapa de compilación, analizando y transformando el árbol de sintaxis abstracta (AST). Esto brinda increíbles posibilidades para optimizar y reducir el código boilerplate sin perder rendimiento.
\n\nEn este artículo, analizaremos técnicas avanzadas de metaprogramación: desde los clásicos macro_rules! hasta macros procedimentales completas. Aprenderás cómo crear tus propios atributos derive y escribir macros de atributos que cambian la semántica del código. ¡Allá vamos!
Macros declarativas: macro_rules! — no es solo "reemplazo de texto"
\n\nMuchos piensan que macro_rules! es un preprocesador de texto primitivo. En realidad, es un potente sistema de coincidencia de patrones que opera a nivel de tokens. Permite crear DSL (lenguajes específicos del dominio) directamente dentro de Rust.
Ejemplo avanzado: creación de un builder con una macro
\n\nImagina que necesitas crear un "constructor" (builder) para una estructura. En lugar de escribir decenas de métodos, puedes escribir una macro:
\n\nmacro_rules! builder {\n ($struct_name:ident, { $($field:ident: $field_type:ty),* $(,)? }) => {\n struct $struct_name {\n $($field: Option<$field_type>),*\n }\n\n impl $struct_name {\n $(\n pub fn $field(mut self, value: $field_type) -> Self {\n self.$field = Some(value);\n self\n }\n )*\n\n pub fn build(self) -> Result<Self, String> {\n $(\n if self.$field.is_none() {\n return Err(format!("Field {} is required", stringify!($field)));\n }\n )*\n Ok(self)\n }\n }\n };\n}\n\nbuilder!(Config, {\n host: String,\n port: u16,\n timeout: u64,\n});\n\nfn main() {\n let config = Config {\n host: None,\n port: None,\n timeout: None,\n }\n .host("localhost".to_string())\n .port(8080)\n .build()\n .unwrap();\n println!("Config ready: {}:{}", config.host.unwrap(), config.port.unwrap());\n}\n\n\nEsta macro no solo genera campos, sino que también agrega validación de campos obligatorios. Observa stringify!($field) — convierte el identificador en una cadena en tiempo de compilación. Esta es una técnica típica de la metaprogramación avanzada en Rust.
Repeticiones y repeticiones: $(...)* en acción
\n\nLa sintaxis de repetición permite procesar listas de argumentos de cualquier longitud. Combinando ident, ty, expr y otros "fragmentos", puedes crear patrones complejos. Por ejemplo, una macro para crear un enum con implementación automática de Display:
macro_rules! display_enum {\n ($enum_name:ident, { $($variant:ident => $desc:expr),* $(,)? }) => {\n enum $enum_name {\n $($variant),*\n }\n\n impl std::fmt::Display for $enum_name {\n fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n match self {\n $(Self::$variant => write!(f, "{}", $desc)),*\n }\n }\n }\n };\n}\n\ndisplay_enum!(Status, {\n Active => "status_active",\n Inactive => "status_inactive",\n Pending => "s