Las macros le permiten escribir código que escribe otro código. Infórmate sobre el extraño y poderoso mundo de la metaprogramación.

La generación de código es una función que encontrará en la mayoría de los lenguajes de programación modernos. Puede ayudarlo a reducir el código repetitivo y la duplicación de código, definir lenguajes específicos de dominio (DSL) e implementar una nueva sintaxis.

Rust proporciona un potente sistema de macros que le permite generar código en tiempo de compilación para una programación más sofisticada.

Introducción a las macros de Rust

Las macros son un tipo de metaprogramación que puede aprovechar para escribir código que escribe código. En Rust, una macro es una pieza de código que genera otro código en tiempo de compilación.

Las macros de Rust son una característica poderosa que le permite escribir código que genera otro código en tiempo de compilación para automatizar tareas repetitivas. Las macros de Rust ayudan a reducir la duplicación de código y aumentan el mantenimiento y la legibilidad del código.

instagram viewer

Puede usar macros para generar cualquier cosa, desde simples fragmentos de código hasta bibliotecas y marcos. Las macros difieren de Funciones de óxido porque operan en código en lugar de datos en tiempo de ejecución.

Definición de macros en Rust

Definirá macros con el macro_reglas! macro. El macro_reglas! macro toma un patrón y una plantilla como entrada. Rust compara el patrón con el código de entrada y usa la plantilla para generar el código de salida.

Así es como puede definir Macros en Rust:

macro_reglas! di hola {
() => {
imprimir!("¡Hola Mundo!");
};
}

fnprincipal() {
¡di hola!();
}

El código define un di hola macro que genera código para imprimir "¡Hola, mundo!". El código coincide con el () sintaxis contra una entrada vacía y la imprimir! macro genera el código de salida.

Este es el resultado de ejecutar la macro en el principal función:

Las macros pueden tomar argumentos de entrada para el código generado. Aquí hay una macro que toma un solo argumento y genera código para imprimir un mensaje:

macro_reglas! decir_mensaje {
($mensaje: expr) => {
imprimir!("{}", $mensaje);
};
}

El decir_mensaje macro toma el $mensaje argumento y genera código para imprimir el argumento usando el imprimir! macro. El expr la sintaxis coincide con el argumento contra cualquier expresión de Rust.

Tipos de macros de óxido

Rust proporciona tres tipos de macros. Cada uno de los tipos de macro tiene propósitos específicos y tienen su sintaxis y limitaciones.

Macros de procedimiento

Las macros de procedimiento se consideran el tipo más potente y versátil. Las macros de procedimiento le permiten definir una sintaxis personalizada que genera código Rust simultáneamente. Puede usar macros de procedimiento para crear macros derivadas personalizadas, macros personalizadas similares a atributos y macros personalizadas similares a funciones.

Utilizará macros derivadas personalizadas para implementar estructuras y características de enumeración automáticamente. Paquetes populares como Serde usan una macro de derivación personalizada para generar código de serialización y deserialización para estructuras de datos de Rust.

Las macros personalizadas similares a atributos son útiles para agregar anotaciones personalizadas al código de Rust. El marco web de Rocket utiliza una macro similar a un atributo personalizado para definir rutas de manera concisa y legible.

Puede usar macros similares a funciones personalizadas para definir nuevas expresiones o declaraciones de Rust. La caja Lazy_static utiliza una macro similar a una función personalizada para definir el inicializado perezoso variables estáticas.

Así es como puede definir una macro de procedimiento que define una macro de derivación personalizada:

usar proc_macro:: TokenStream;
usar cita:: cita;
usar syn::{DeriveInput, parse_macro_input};

El usar Las directivas importan las cajas y los tipos necesarios para escribir una macro de procedimiento de Rust.

#[proc_macro_derive (MyTrait)]
pubfnmi_macro_derivado(entrada: TokenStream) -> TokenStream {
dejar ast = parse_macro_input!(entrada como DerivarEntrada);
dejar nombre = &ast.ident;

dejar gen = cotización! {
impl mi rasgo para #nombre {
// implementacion aqui
}
};

gen.into()
}

El programa define una macro de procedimiento que genera la implementación de un rasgo para una estructura o enumeración. El programa invoca la macro con el nombre mi rasgo en el atributo de derivación de la estructura o enumeración. La macro toma un TokenStream objeto como entrada que contiene el código analizado en un árbol de sintaxis abstracta (AST) con el parse_macro_input! macro.

El nombre variable es la estructura derivada o el identificador de enumeración, el ¡cita! La macro genera un nuevo AST que representa la implementación de mi rasgo para el tipo que finalmente se devuelve como TokenStream con el en método.

Para usar la macro, deberá importar la macro desde el módulo en el que la declaró:

// asumiendo que declaró la macro en un módulo my_macro_module

usar my_macro_module:: my_derive_macro;

Al declarar la estructura o enumeración que usa la macro, agregará el #[derivar (MiCaracterística)] atributo a la parte superior de la declaración.

#[derivar (MiCaracterística)]
estructuraMiEstructura {
// campos aquí
}

La declaración de estructura con el atributo se expande a una implementación de la mi rasgo rasgo para la estructura:

impl mi rasgo para MiEstructura {
// implementacion aqui
}

La implementación le permite usar métodos en el mi rasgo rasgo en MiEstructura instancias.

Macros de atributos

Las macros de atributos son macros que puede aplicar a elementos de Rust como estructuras, enumeraciones, funciones y módulos. Las macros de atributos toman la forma de un atributo seguido de una lista de argumentos. La macro analiza el argumento para generar código Rust.

Utilizará macros de atributos para agregar comportamientos personalizados y anotaciones a su código.

Aquí hay una macro de atributos que agrega un atributo personalizado a una estructura de Rust:

// importando módulos para la definición de la macro
usar proc_macro:: TokenStream;
usar cita:: cita;
usar syn::{parse_macro_input, DeriveInput, AttributeArgs};

#[proc_macro_atributo]
pubfnmi_atributo_macro(atributo: TokenStream, artículo: TokenStream) -> TokenStream {
dejar argumentos = parse_macro_input!(atributo como AttributeArgs);
dejar input = parse_macro_input!(elemento como DerivarEntrada);
dejar nombre = &entrada.ident;

dejar gen = cotización! {
#aporte
impl #nombre {
// comportamiento personalizado aquí
}
};

gen.into()
}

La macro toma una lista de argumentos y una definición de estructura y genera una estructura modificada con el comportamiento personalizado definido.

La macro toma dos argumentos como entrada: el atributo aplicado a la macro (analizado con el parse_macro_input! macro) y el elemento (analizado con el parse_macro_input! macro). La macro utiliza el ¡cita! macro para generar el código, incluido el elemento de entrada original y un adicional impl bloque que define el comportamiento personalizado.

Finalmente, la función devuelve el código generado como un TokenStream con el en() método.

Reglas de macros

Las reglas de macro son el tipo de macro más sencillo y flexible. Las reglas de macro le permiten definir una sintaxis personalizada que se expande al código Rust en tiempo de compilación. Las reglas de macro definen macros personalizadas que coinciden con cualquier expresión o declaración de óxido.

Utilizará reglas macro para generar código repetitivo para abstraer detalles de bajo nivel.

Así es como puede definir y usar reglas de macro en sus programas de Rust:

macro_reglas! hacer_vector {
( $( $x: expr ),* ) => {
{
dejarmudo v = Vec::nuevo();
$(
v.push($x);
)*
v
}
};
}

fnprincipal() {
dejar v = hacer_vector![1, 2, 3];
imprimir!("{:?}",v); // imprime "[1, 2, 3]"
}

El programa define un hacer_vector! una macro que crea un nuevo vector a partir de una lista de expresiones separadas por comas en el principal función.

Dentro de la macro, la definición del patrón coincide con los argumentos pasados ​​a la macro. El $( $x: expr ),* la sintaxis coincide con cualquier expresión separada por comas identificada como $ x.

El $( ) la sintaxis en el código de expansión itera sobre cada expresión en la lista de argumentos pasados ​​a la macro después el paréntesis de cierre, lo que indica que las iteraciones deben continuar hasta que la macro procese todos los expresiones

Organice sus proyectos de óxido de manera eficiente

Las macros de Rust mejoran la organización del código al permitirle definir patrones y abstracciones de código reutilizables. Las macros pueden ayudarlo a escribir código más conciso y expresivo sin duplicaciones en varias partes del proyecto.

Además, puede organizar los programas de Rust en cajas y módulos para una mejor organización del código, reutilización e interoperabilidad con otras cajas y módulos.