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ónsaludar_derive. Contiene la representación de tokens del elemento (en este caso, elstructoenum) sobre el cual se ha invocado#[derive(Saludar)].parse_macro_input!(input as DeriveInput): Utiliza la cratesynpara transformar el flujo de tokens crudos en una estructuraDeriveInput. Esta estructura contiene campos útiles comoident(el nombre del tipo) ydata(la definición de sus campos o variantes).input.ident: Es un campo deDeriveInputque contiene el identificador del tipo. Lo almacenamos ennombre_tipopara poder referenciarlo en la generación del código.quote! { ... }: Es una macro de la cratequoteque permite escribir código Rust como una plantilla. El símbolo#nombre_tipoes una instrucción de interpolación que sustituye esa posición por el valor de la variablenombre_tipoen 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 traitSaludarpara el tipo que el usuario haya definido.TokenStream::from(expansion): La macro debe devolver unTokenStream.quote!genera un objetoTokenStream(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.
N° 101