Este tutorial explica la diferencia entre las funciones y las macros tipo función. Por ejemplo, ¿por qué msg! tiene un signo de exclamación al final? Este tutorial explicará esta sintaxis.
Al ser un lenguaje fuertemente tipado, Rust no puede aceptar un número arbitrario de argumentos en una función.
Por ejemplo, la función print de Python puede aceptar un número arbitrario de argumentos:
print(1)
print(1, 2)
print(1, 2, 3)
El ! denota que la “función” es una macro tipo función.
Las macros tipo función de Rust se identifican por la presencia de un símbolo !, por ejemplo en println!(...) o msg!(...) en Solana.
En Rust, una función regular (no una macro tipo función) para imprimir algo es std::io::stdout().write y solo acepta una única cadena de bytes (byte string) como argumento.
Si deseas ejecutar el siguiente código, el Rust Playground es una herramienta conveniente si no quieres configurar un entorno de desarrollo.
Usemos el siguiente ejemplo (tomado de aquí):
use std::io::Write;
fn main() {
std::io::stdout().write(b"Hello, world!\n").unwrap();
}
Ten en cuenta que write es una función, no una macro, ya que no tiene el !.
Si intentas hacer lo que hicimos arriba en Python, el código no compilará porque write solo acepta un argumento:
// this does not compile
use std::io::Write;
fn main() {
std::io::stdout().write(b"1\n").unwrap();
std::io::stdout().write(b"1", b"2\n").unwrap();
std::io::stdout().write(b"1", b"2", b"3\n").unwrap();
}
Por lo tanto, si deseas imprimir un número arbitrario de argumentos, necesitas escribir una función print personalizada para manejar cada caso para cada número de argumentos — ¡lo cual es extremadamente ineficiente!
Así es como se vería dicho código (¡esto es muy poco recomendable!):
use std::io::Write;
// print one argument
fn print1(arg1: &[u8]) -> () {
std::io::stdout().write(arg1).unwrap();
}
// print two arguments
fn print2(arg1: &[u8], arg2: &[u8]) -> () {
let combined_vec = [arg1, b" ", arg2].concat();
let combined_slice = combined_vec.as_slice();
std::io::stdout().write(combined_slice).unwrap();
}
// print three arguments
fn print3(arg1: &[u8], arg2: &[u8], arg3: &[u8]) -> () {
let combined_vec = [arg1, b" ", arg2, b" ", arg3].concat();
let combined_slice = combined_vec.as_slice();
std::io::stdout().write(combined_slice).unwrap();
}
fn main() {
print1(b"1\n");
print2(b"1", b"2\n");
print3(b"1", b"2", b"3\n");
}
Si buscamos un patrón en las funciones print1, print2, print3, es simplemente insertar los argumentos en un vector y agregar un espacio entre ellos, para luego convertir el vector nuevamente en una cadena de bytes (un bytes slice para ser exactos).
¿No sería genial si pudiéramos tomar un fragmento de código como println! y expandirlo automáticamente en una función print que tome exactamente tantos argumentos como necesitemos?
Esto es lo que hace una macro en Rust.
Una macro en Rust toma código de Rust como entrada y lo expande programáticamente en más código de Rust.
Esto nos ayuda a evitar el tedio de tener que escribir una función print para cada tipo de declaración print que requiera nuestro código.
Expandiendo la macro
Para ver un ejemplo de cómo el compilador de Rust está expandiendo la macro println!, revisa el repositorio de github de cargo expand. El resultado es bastante extenso, así que no lo mostraremos aquí.
Está bien tratar las macros como cajas negras
Las macros son muy útiles cuando las proporciona una biblioteca, pero son muy tediosas de escribir a mano, ya que requieren analizar (parsing) literalmente el código de Rust.
Diferentes tipos de macros en Rust
El ejemplo que hemos dado con println! es una macro tipo función. Rust tiene otros tipos de macros, pero las otras dos que nos importan son el custom derive macro y el attribute-like macro.
Veamos un programa nuevo creado por anchor:

Explicaremos cómo funcionan en el siguiente tutorial.
Aprende más con RareSkills
Este tutorial es parte de nuestro curso de Solana gratuito.
Publicado originalmente el 15 de febrero de 2024