ProgramaciónArquitecto de aplicación/biblioteca Rust

¿Qué son los macros en Rust? Tipos de macros, la diferencia entre procedural y declarative, cuándo es mejor elegir uno de ellos y qué peligros pueden surgir al utilizarlos?

Supere entrevistas con el asistente de IA Hintsage

Respuesta

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:

  • Macros declarativas (macro_rules!): se definen mediante una sintaxis similar a match, funcionando bajo el principio de plantilla->sustitución. El tipo más típico es macro_rules!.
  • Macros procedimentales: se declaran como funciones externas en un crate, reciben AST (TokenStream) y devuelven código modificado. Se dividen en #[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.

Pregunta con trampa

¿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 } }

Ejemplos de errores reales debido a la falta de conocimiento en el tema


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.