Macros en Rust entre archivos: tutorial completo para desarrollo modular

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Macros en Rust: ¿cómo hacer que se lleven bien con los módulos?



¿Alguna vez has escrito una macro útil en Rust y luego has intentado usarla en un módulo vecino, recibiendo un misterioso error del compilador? Si es así, no estás solo. La pregunta "How do I use a macro across module files?" ha acumulado miles de visitas en Stack Overflow, y no es casualidad.



En Rust, las macros son una poderosa herramienta de metaprogramación. Permiten generar código sobre la marcha, evitar la duplicación y escribir DSL (lenguajes específicos del dominio). Pero hay un problema: las macros no se comportan como las funciones normales. Tienen sus propias reglas de visibilidad, y los principiantes a menudo tropiezan con esto.



En este tutorial, te mostraré tres formas probadas de organizar macros entre archivos de módulos. ¡Allá vamos!



¿Por qué las macros no funcionan "por defecto"?



Antes de sumergirnos en la solución, entendamos la causa. En Rust, las funciones y los tipos son visibles solo en su propio módulo por defecto (a menos que estén marcados con pub). Con las macros pasa lo mismo, pero hay un matiz: pub para macros no funciona igual que para las funciones.



Consideremos un ejemplo simple. Supongamos que tenemos un proyecto con esta estructura:



src/├── main.rs├── helpers/│   ├── mod.rs│   └── macros.rs


En macros.rs declaramos una macro:



// src/helpers/macros.rsmacro_rules! greet {    ($name:expr) => {        println!("¡Hola, {}!", $name);    };}


Ahora queremos usarla en main.rs:



// src/main.rsmod helpers;

fn main() { greet!("Mundo"); // ¡Error! La macro no es visible.}


El compilador mostrará algo como: error: macro `greet` is not imported. ¿Por qué? Porque las macros no se exportan por defecto desde el módulo, incluso si el módulo se declara como pub.



Método 1: El atributo #[macro_use] para importar macros



La forma más sencilla es usar el atributo #[macro_use] antes de la declaración del módulo. Esto obliga a Rust a importar todas las macros de ese módulo al ámbito actual.



Corrijamos nuestro ejemplo:



// src/main.rs#[macro_use]mod helpers;

fn main() { greet!("Mundo"); // ¡Ahora funciona!}


Pero hay un inconveniente: #[macro_use] importa todas las macros del módulo. Si tienes muchas macros, esto puede provocar conflictos de nombres. Además, este enfoque se considera obsoleto en Rust moderno (a partir de la edición 2018).



¿Dónde sigue siendo útil? En proyectos pequeños o cuando estás escribiendo un prototipo y quieres conectar rápidamente un par de macros sin complicaciones.



Matiz importante: orden de declaración



Si usas #[macro_use], las macros estarán disponibles inmediatamente después de la declaración del módulo. Esto significa que el siguiente código no funcionará:



// src/main.rsfn use_greet() {    greet!("Prueba"); // ¡Error! El módulo aún no está declarado.}

#[macro_use]mod helpers;

fn main() { use_greet();}


El orden importa. Tenlo en cuenta.



Método 2: Exportación explícita de macros con #[macro_export]



Una forma más moderna y controlada es usar el atributo #[macro_export] directamente en la definición de la macro. Esto hace que la macro esté disponible a nivel de crate, no solo del módulo.



Así es como funciona:



// src/helpers/macros.rs#[macro_export]macro_rules! greet {    ($name:expr) => {        println!("¡Hola, {}!", $name);    };}


Ahora en main.rs podemos importar la macro explícitamente con use:



// src/main.rsmod helpers;

use helpers::greet;

fn main() { greet!("Mundo"); // ¡Funciona!}


Observa: usamos use helpers::greet, no use hel

Blogs

Book Recommendations