Proceso de compilación de Rust
La primera etapa es la tokenización, donde se convierte el texto en símbolos (unidades indivisibles del lenguaje, son como las palabras de Rust). Algunos ejemplos de tokens:
- Identifiers:
foo
,Bambous
,self
,we_can_dance
, … - Integers:
42
,72u32
,0_______0
, … - Keywords:
_
,fn
,self
,match
,yield
,macro
, … - Lifetimes:
'a
,'b
,'a_rare_long_lifetime_name
, … - Strings:
""
,"Leicester"
,r##"venezuelan beaver"##
, … - Symbols:
[
,:
,::
,->
,@
,<-
, …
Notas:
self
es un identificador y una palabra clave (normalmente este último).yield
ymacro
no son palabras clave como tal, pero el compilador las interpreta así.<-
se eliminó de la gramática pero no del lexer.::
no son dos:
, sino que se trata de un símbolo totalmente diferente.
En este punto, algunos lenguajes procesan aquí los macros, por eso esto funciona:
1#include <stdio.h>
2
3#define SUB void
4#define BEGIN {
5#define END }
6
7SUB main() BEGIN
8 printf("Pesadillas!\n");
9END
La siguiente fase es el Parser, donde los tokens se cambian por el Árbol Sintáctico Abstracto (AST) que contiene la estructura del programa entero.
Una vez hecho eso, es cuando los macros se procesan.
Macros
Las macros permiten escribir código que escriba otro código, lo que se llama metaprogramming, es decir, definiremos una especie de función que se ejecutará y como resultado modificará nuestro archivo de código fuente.
Las macros son similares a las funciones, pero sin el coste extra en la hora de ejecución, aunque se aumenta el coste de compilación.
Nota: Los macros en Rust son muy diferentes a otros lenguajes como por ejemplo C, que solo es substitución de texto.
Metaprogramming se usa para reducir la cantidad de código que tienes que escribir y mantener, que es el mismo motivo de las funciones, pero los macros ofrecen otras posibilidades.
Tipos de macros
- Declarativos: similares a un
match
y simplemente reemplazan la declaración del macro (terminada en!
) por otro código. - Procedurales: operan en el ATS (Abstract Syntax Tree) de Rust. Estes tipos de
macros son funciones que afectan un
TokenStream
cuando pasa a otro, donde el macro reemplaza algunos de ellos.
Esto del
TokenStream
es como funciona el compilado de Rust. En lugar de cambiar directamente el contenido del archivo, se modifican los símbolos que el compilador ha entendió.
Macros declarativos
Se crean con macro_rules!
. Son algo menos poderosos, pero son bastante
sencillos para acortar código y eliminar partes duplicadas. Uno de los más
comunes de este tipo es println!
.
1// Indica que este macro se puede usar en cualquier lugar dentro del crate.
2// Sin esta anotación, al macro no podrá entrar en scope
3
4#[macro_export]
5
6// Aquí no es necesario añadir la `!`
7macro_rules! <macro_name> {
8 // Esta sintáxis es similar a un match
9 ($a: expr, $b: expr) => {
10 { // Añadimos ´{}´ extra en caso de que creemos una variable.
11 // Al terminarse el macro, el compilador se encontrará con `}` y
12 // borrará el valor, por lo que no tendremos errores inesperados
13 // los valores $a y $b se sustituiran y luego se sumarán
14 $a + $b
15 }
16 };
17
18 // Se pueden añadir varios brazos para usar diferentes argumentos
19 // en el mismo macro
20 ($a: expr) => {
21 {
22 $a
23 }
24 };
25}
También podemos pasarle a un macro un número indeterminado de argumentos, *
se
usa para 0 o más y +
para 0 o 1. La parte que se repite debe ir entre $()
luego una coma y *
/+
.
1#[macro_export]
2macro_rules! vec {
3 ( $( $x:expr ), * ) => {
4 {
5 let mut temp_vec = Vec::new();
6 $(
7 temp_vec.push($x);
8 )*
9 temp_vec
10 }
11 };
12}
Estos son los símbolos que representan los valores precedidos de $
:
item
: función, struct, módulo, etcblock
: algo redondeado por{}
(?)stmt
: sentenciapat
: patrónexpr
: expresiónty
: tipoident
: identificadorpath
: una dirección, por ejemplo::mem::replace
meta
: lo que va dentro de#[...]
#![...]
tt
: a single token treevis
:pub
(?)
Macros procedurales
Existen tres tipos:
#[derive]
- Attribute-like macros that define custom attributes usable on any item
- Function-like macros that look like function calls but operate on the tokens specified as their argument