Macros en Rust

Las macros en Rust son de esas cosas que son geniales pero a la vez son algo complicadas. Voy a intentar hacerlo fácil yendo paso a paso.

¿Qué es una macro en Rust?

Una macro lo que hace es generar código.

Y lo hace antes de la compilación.

Ojo que esto es importante. Es una forma de ahorrarte trabajo de teclear código. Y, pensarás, ¿pero eso no es lo que hace una función? Cierto, puede parecer lo mismo pero no. Una función entra en acción cuando el programa se está ejecutando. La macro hace su trabajo antes de que el programa se haya generado.

¿Quieres apuntarte al «Reto diario de Rust»?

Es una forma cómoda y divertida de aprender Rust. Cada día recibes en tu email un pequeño concepto de Rust y luego tendrás que contestar a una pregunta.

De esta forma, poco a poco, pero sin descanso, irás adentrándote de manera divertida y sencilla en el apasionante mucho de Rust.

Para apuntarte solo necesitas entrar en este enlace y apuntarte:

https://urlantools.urlanheat.com/newsletter/coding-crabs-es/subscribe?o=blogcrabs

Ejemplo de una macro súper simple

Vamos a empezar con la macro más sencilla que se me ocurre. Sería una tontería como ésta:

macro_rules! hola {
    () => {
        "Soy una macro que saluda!"
    }
}

Esta macro se usaría así:

pub fn main() {
    println!(hola!());
}

Y esto es exactamente lo mismo que haber hecho ésto:

pub fn main() {
    println!("Soy una macro que saluda!");
}

Pero lo mismo, mismo. Antes de compilar, Rust se encarga de aplicar las macros que haya y generar el código que haga falta.

Pero este código que he puesto es una tontería grandísima. Vamos a dar otro pasito con otro ejemplo estúpido:

macro_rules! hola {
    ( $nombre:expr ) => {
        println!("{}", $nombre);
    }
}

pub fn main() {
    hola!("Hola Gorka!");
}

Esto va a generar este código:

pub fn main() {
    println!("{}", "Hola Gorka!");
}

Aquí ya se empieza a complicar la cosa. Puede que te sorprenda que el código generado sea:

"{}", "Hola Gorka!"

Y no:

"Hola Gorka!"

Pero veremos luego por qué es así.

En este ejemplo tenemos algo que parece un parámetro pero que no lo es. En Rust las macros tienen un formato como éste:

macro_rules! nombre_de_la_macro {
    ( rama ) => {
        // código
    }
}

Una macro se define por su nombre (nombre_de_la_macro) y luego puede tener una o varias ramas. En los dos ejemplos que he puesto solo hay una rama. Pero vamos a ver más ejemplos luego con más ramas. Así que por ahora te basta con saber que hay algo llamado ramas.

Dento de cada rama tenemos un trozo de código. Ah, no he mencionado que el código generado por Rust no incluye las llaves ({}) de la rama. Es decir, en este ejemplo:

macro_rules! hola {
    ( $nombre:expr ) => {
        println!("{}", $nombre);
    }
}

pub fn main() {
    hola!("Hola Gorka!");
}

el código que se genera es:

pub fn main() {
    println!("{}", Hola Gorka!");
}

y no:

pub fn main() {
  {
    hola!("{}", "Hola Gorka!");
  }
}

Esto parece una tontería pero a veces puede despistar así que hay que tenerlo claro.

Bueno, hemos visto que en este ejemplo tenemos esta rama:

( $nombre:expr )

Esta cosa rara significa que estamos buscando una expresión (por eso lo de «expr») y a esa expresión vamos a llamarla $nombre para usarla más adelante.

Cuando llamemos a la macro así:

hola!("Hola Gorka!");

Rust dirá ¡ah, vale! Quieres que coja la expresión «Hola Gorka!» y te la guarde en $nombre para usarla luego.

Así, cuando hagamos lo de:

println!("{}", $nombre);

Rust lo sustituirá por:

println!("{}", "Hola Gorka!");

¿Y si falta algún token?

Cuando hablamos de las macros tenemos algo llamado tokens que no tienen nada que ver con criptomonedas. Son los «parámetros» que usará una macro para generar el código.

En nuestro ejemplo:

hola!("Hola Gorka!");

el token era:

"Hola Gorka!"

¿Y qué pasa si me olvido del token? Por ejemplo si llamo a la macro hola!() así:

hola!();

Pues que Rust se va a quejar cuando compiles y verás un error como éste:

missing tokens in macro arguments

Usar varias ramas

¿Y qué habría que hacer para tener una macro a la que pueda no pasarle nada? Para eso podemos usar varias «ramas» tal que así:

macro_rules! hola {
    () => {
        println!("Hola, persona desconocida!");
    };

    ( $nombre:expr ) => {
        println!("{}", $nombre);
    }
}

Ahora podemos llamar a la macro con un texto o sin él:

pub fn main() {
    hola!();
    hola!("Hola Gorka!");
}

Si no pasamos ningún token entraremos por la rama que no tiene nada, la del paréntesis vacío. Y si metemos un texto entaremos por la segunda.

Importante, si tienes más de una rama tienes que separar cada una de ellas con un punto y coma (;).

Deja un comentario