29 - Macros en Rust - metaprogramación poderosa

29 - Macros en Rust - metaprogramación poderosa

¡Bienvenidos de nuevo a Rustaceo.es! Hoy nos adentraremos en uno de los aspectos más avanzados y poderosos de Rust: las macros. Gracias a la metaprogramación, las macros nos permiten escribir código más flexible, evitar repetición y generar estructuras de código en tiempo de compilación. ¡Acompáñame en esta exploración!


🔎 ¿Qué es una macro en Rust? #

En Rust, una macro es un mecanismo de metaprogramación que nos permite generar código de manera dinámica. Existen distintos tipos de macros, pero las más utilizadas son:

  1. Macros de Declaración (macro_rules!): Definen patrones que expanden en código Rust.
  2. Macros Procedurales: Se escriben como funciones especiales que operan sobre el código fuente.
  3. Macros Derive: Generan implementaciones de traits automáticamente.

Veamos cada una en detalle con ejemplos del mundo Pokémon.


🛠 Macros de declaración (macro_rules!) #

Las macros de declaración son el tipo más común en Rust y permiten definir reglas de expansión de código.

📌 Ejemplo: macro para crear pokémon #

macro_rules! crear_elemento {
    ($nombre:expr, $tipo:expr, $nivel:expr) => {
        struct Pokémon {
            nombre: String,
            tipo: String,
            nivel: u8,
        }

        let pokemon = Pokémon {
            nombre: String::from($nombre),
            tipo: String::from($tipo),
            nivel: $nivel,
        };

        println!("{} es un Pokémon de tipo {} y nivel {}!", pokemon.nombre, pokemon.tipo, pokemon.nivel);
    };
}

fn main() {
    crear_elemento!("Item1", "Eléctrico", 25);
}

En este ejemplo:

  • macro_rules! define una macro llamada crear_elemento!.
  • Acepta tres parámetros (nombre, tipo y nivel).
  • Expande en código que crea una estructura y una instancia de Pokémon.

Resultado esperado:

Item1 es un Pokémon de tipo Eléctrico y nivel 25!

🔄 Iteraciones con macros #

Podemos usar macros para iterar y generar múltiples elementos.

macro_rules! generar_elemento {
    ($($nombre:expr, $tipo:expr, $nivel:expr);*) => {
        $(
            println!("{} es de tipo {} y nivel {}!", $nombre, $tipo, $nivel);
        )*
    };
}

fn main() {
    generar_elemento!("Item2", "Fuego", 10; "Item3", "Agua", 12; "Item4", "Planta", 11);
}

Este código genera varias líneas de salida sin escribirlas manualmente.


⚙️ Macros procedurales #

Las macros procedurales son funciones que operan sobre el código fuente en tiempo de compilación. Se escriben en un crate separado y permiten manipular estructuras de Rust.

📌 Ejemplo: macro derive personalizada #

Supongamos que queremos imprimir la información de cualquier estructura Pokémon sin escribir manualmente Debug.

use proc_macro::TokenStream;
use quote::quote;
use syn;

#[Proc_macro_derive(descripcion)]
pub fn descripcion_macro(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    let nombre = &ast.ident;
    let gen = quote! {
        impl #nombre {
            pub fn descripcion(&self) {
                println!("Este es un Pokémon de tipo {:?} y nivel {:?}", self.tipo, self.nivel);
            }
        }
    };
    gen.into()
}

Luego, en nuestro código principal:

#[Derive(descripcion)]
struct Pokémon {
    nombre: String,
    tipo: String,
    nivel: u8,
}

fn main() {
    let item1 = Pokémon {
        nombre: String::from("Item1"),
        tipo: String::from("Eléctrico"),
        nivel: 25,
    };
    item1.descripcion();
}

Esto generará automáticamente el método descripcion() para la estructura Pokémon.


🎭 Macros en derive #

Las macros derive nos permiten implementar automáticamente traits como Debug, Clone, Default, entre otros.

Ejemplo con Debug:

#[Derive(debug)]
struct Pokémon {
    nombre: String,
    tipo: String,
    nivel: u8,
}

fn main() {
    let item2 = Pokémon {
        nombre: String::from("Item2"),
        tipo: String::from("Fuego"),
        nivel: 16,
    };
    println!("{:?}", item2);
}

Salida esperada:

Pokémon { nombre: "Item2", tipo: "Fuego", nivel: 16 }

📌 Conclusión #

Las macros en Rust son una herramienta poderosa para automatizar la generación de código y evitar la repetición. Desde macro_rules! hasta macros procedurales y derive, Rust nos ofrece múltiples formas de escribir código más eficiente y flexible.

🔮 Próximo paso: ¡Experimenta con macros en tus proyectos y automatiza tareas repetitivas!

¡Hasta la próxima, Rustaceos!