Las derive macros son un tipo especializado de macros procedurales que actúan como extensiones del compilador. Su función es implementar automáticamente un trait para un tipo (struct o enum) mediante la instrucción #[derive(NombreDelTrait)]. A diferencia de las macros declarativas (macro_rules!), que realizan sustitución de texto, las derive macros operan sobre el AST (Abstract Syntax Tree), permitiendo una manipulación estructural profunda del código.

El proceso ocurre durante la fase de expansión de macros en la compilación. El compilador identifica el atributo #[derive], toma los tokens que definen el tipo (su nombre, campos, atributos, etc.) y los envía a la función de la macro como un TokenStream. La macro procesa esta información y debe devolver un nuevo TokenStream que contenga la implementación del trait. Si la macro devuelve un código inválido o no maneja correctamente los tokens de entrada, el proceso de compilación fallará con errores de sintaxis que pueden ser difíciles de depurar para el usuario final.

Para implementar esto, es obligatorio que la macro resida en un crate separado con la configuración [lib] proc-macro = true en su Cargo.toml. En la práctica, se utilizan dos librerías estándar: syn, para transformar el TokenStream crudo en una estructura de datos parseable (AST), y quote, para realizar el proceso inverso (quasi-quoting), facilitando la construcción de nuevos fragmentos de código mediante plantillas.

// Este código debe residir en un crate con: [lib] proc-macro = true
// Dependencias necesarias: syn = { version = "2.0", features = ["full"] }
//                         quote = "1.0"

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

/// Esta función es la implementación de la derive macro.
/// Recibe el TokenStream que representa el struct/enum y devuelve el código generado.
#[proc_macro_derive(Saludar)]
pub fn saludar_derive(input: TokenStream) -> TokenStream {
    // 1. Parsear el TokenStream de entrada en una estructura AST (DeriveInput)
    let input = parse_macro_input!(input as DeriveInput);

    // 2. Obtener el identificador del tipo (el nombre del struct o enum)
    let nombre_tipo = &input.ident;

    // 3. Generar el nuevo código utilizando la macro quote!
    // Usamos #nombre_tipo para interpolar el identificador en el código generado.
    let expansion = quote! {
        impl Saludar for #nombre_tipo {
            fn saludar(&self) {
                println!("Hola desde la estructura: {}", stringify!(#nombre_tipo));
            }
        }
    };

    // 4. Convertir la expansión de vuelta a un TokenStream para el compilador
    TokenStream::from(expansion)
}

// --- Ejemplo de uso (en el crate principal que consume la macro) ---
// trait Saludar { fn saludar(&self); }
// #[derive(Saludar)]
// struct Persona { nombre: String }
// fn main() { Persona { nombre: "Rust".into() }.saludar(); }

Explicación del Código

  • input: TokenStream: Es el parámetro de entrada de la función saludar_derive. Contiene la representación de tokens del elemento (en este caso, el struct o enum) sobre el cual se ha invocado #[derive(Saludar)].
  • parse_macro_input!(input as DeriveInput): Utiliza la crate syn para transformar el flujo de tokens crudos en una estructura DeriveInput. Esta estructura contiene campos útiles como ident (el nombre del tipo) y data (la definición de sus campos o variantes).
  • input.ident: Es un campo de DeriveInput que contiene el identificador del tipo. Lo almacenamos en nombre_tipo para poder referenciarlo en la generación del código.
  • quote! { ... }: Es una macro de la crate quote que permite escribir código Rust como una plantilla. El símbolo #nombre_tipo es una instrucción de interpolación que sustituye esa posición por el valor de la variable nombre_tipo en la fase de expansión.
  • impl Saludar for #nombre_tipo: Esta es la parte crucial de la generación; estamos construyendo dinámicamente la implementación del trait Saludar para el tipo que el usuario haya definido.
  • TokenStream::from(expansion): La macro debe devolver un TokenStream. quote! genera un objeto TokenStream (o similar compatible) que se convierte de nuevo al tipo requerido por la firma de la función de la macro para que el compilador pueda integrarlo en el programa principal.

101