En Rust, los macros permiten generar código en tiempo de compilación, proporcionando herramientas poderosas para la metaprogramación, reducción de boilerplate e implementación de DSL. Los principales tipos de macros:
macro_rules!.#[derive], atributos (#[some_macro]) y similares a funciones (custom_macro!()).Las declarativas son más simples de usar para código plantilla, mientras que las procedimentales ofrecen mayor control sobre la sintaxis y el análisis de tokens.
Ejemplo de macro declarativa:
macro_rules! vec_of_strings { ($($x:expr),*) => { { let mut v = Vec::new(); $(v.push($x.to_string());)* v } }; } let v = vec_of_strings!("a", "b"); // => Vec<String>
Macro procedimental (ejemplo derive):
#[derive(Debug, Clone)] struct MyStruct; // el derive Debug se implementa como un macro procedimental en std.
¿Pueden los macros de Rust generar código sintácticamente incorrecto o código con errores en tiempo de ejecución?
Respuesta: Sí, los macros no verifican la corrección de la expansión en el momento de la escritura; los errores pueden manifestarse solo después de que el compilador expande el macro. Los macros declarativos pueden llevar a errores sintácticos poco evidentes. Los macros procedimentales pueden generar código incorrecto o vulnerable, por lo que es crucial probar exhaustivamente su funcionamiento.
Ejemplo:
macro_rules! make_error { () => { let x = ; // un error de sintaxis ocurrirá al usar el macro } }
Historia
En un gran proyecto, para reducir el boilerplate se usaron macro_rules!, sin cubrir todos los casos de patrones. Un usuario accidentalmente pasó una expresión no soportada al macro, lo que llevó a un error de compilación confuso, cuya causa era difícil de rastrear.
Historia
Al trasladar macros procedimentales entre crates surgieron problemas de incompatibilidad entre versiones de la API de TokenStream, lo que hizo que la IDE se colapsara y el error solo se manifestara en compilaciones no_std.
Historia
Al escribir un DSL para configuraciones con un macro procedimental, se realizó un análisis inseguro de los tokens de entrada (sin validación de tipos), lo que provocó extraños errores en tiempo de ejecución, vulnerabilidades y la incapacidad de desplegar correctamente parte de nueva funcionalidad.