As macros permitem que você escreva um código que escreve outro código. Descubra o estranho e poderoso mundo da metaprogramação.

A geração de código é um recurso que você encontrará na maioria das linguagens de programação modernas. Ele pode ajudá-lo a reduzir o código clichê e a duplicação de código, definir idiomas específicos de domínio (DSLs) e implementar nova sintaxe.

Rust fornece um poderoso sistema de macro que permite gerar código em tempo de compilação para uma programação mais sofisticada.

Introdução às Macros Rust

Macros são um tipo de metaprogramação que você pode aproveitar para escrever código que escreve código. No Rust, uma macro é um pedaço de código que gera outro código em tempo de compilação.

Rust macros são um recurso poderoso que permite escrever código que gera outro código em tempo de compilação para automatizar tarefas repetitivas. As macros do Rust ajudam a reduzir a duplicação de código e aumentam a capacidade de manutenção e legibilidade do código.

Você pode usar macros para gerar qualquer coisa, desde simples trechos de código até bibliotecas e estruturas. Macros diferem de

funções de ferrugem porque eles operam em código em vez de dados em tempo de execução.

Definindo Macros no Rust

Você definirá macros com o macro_regras! macro. O macro_regras! macro recebe um padrão e um modelo como entrada. Rust combina o padrão com o código de entrada e usa o modelo para gerar o código de saída.

Veja como você pode definir Macros no Rust:

macro_regras! diga olá {
() => {
println!("Olá Mundo!");
};
}

fnprincipal() {
diga olá!();
}

O código define um diga olá macro que gera código para imprimir "Hello, world!". O código corresponde ao () sintaxe contra uma entrada vazia e o println! macro gera o código de saída.

Aqui está o resultado da execução da macro no principal função:

As macros podem receber argumentos de entrada para o código gerado. Aqui está uma macro que recebe um único argumento e gera código para imprimir uma mensagem:

macro_regras! diga_mensagem {
($mensagem: expr) => {
println!("{}", $mensagem);
};
}

O diga_mensagem macro pega o $mensagem argumento e gera código para imprimir o argumento usando o println! macro. O expr A sintaxe corresponde ao argumento contra qualquer expressão Rust.

Tipos de macros de ferrugem

Rust fornece três tipos de macros. Cada um dos tipos de macro atende a propósitos específicos e possui sua sintaxe e limitações.

Macros processuais

As macros de procedimento são consideradas o tipo mais poderoso e versátil. As macros procedurais permitem que você defina a sintaxe personalizada que gera o código Rust simultaneamente. Você pode usar macros procedurais para criar macros de derivação personalizadas, macros semelhantes a atributos personalizados e macros semelhantes a funções personalizadas.

Você usará macros de derivação personalizadas para implementar structs e características de enumeração automaticamente. Pacotes populares como Serde usam uma macro de derivação personalizada para gerar código de serialização e desserialização para estruturas de dados Rust.

Macros personalizadas semelhantes a atributos são úteis para adicionar anotações personalizadas ao código Rust. A estrutura da Web do Rocket usa uma macro personalizada semelhante a um atributo para definir rotas de forma concisa e legível.

Você pode usar macros semelhantes a funções personalizadas para definir novas expressões ou declarações Rust. A caixa Lazy_static usa uma macro de função personalizada para definir o inicialização preguiçosa variáveis ​​estáticas.

Veja como você pode definir uma macro de procedimento que define uma macro de derivação personalizada:

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

O usar As diretivas importam as caixas e os tipos necessários para escrever uma macro de procedimento Rust.

#[proc_macro_derive (MyTrait)]
barfnminha_deriva_macro(entrada: TokenStream) -> TokenStream {
deixar ast = parse_macro_input!(entrada como DeriveInput);
deixar nome = &ast.ident;

deixar gen = citação! {
implicar MyTrait para #nome {
// implementação aqui
}
};

gen.into()
}

O programa define uma macro procedural que gera a implementação de um trait para uma struct ou enum. O programa chama a macro com o nome MyTrait no atributo derivar da estrutura ou enum. A macro leva um TokenStream objeto como entrada contendo o código analisado em uma Abstract Syntax Tree (AST) com o parse_macro_input! macro.

O nome variável é a estrutura derivada ou identificador de enumeração, o citar! A macro gera um novo AST representando a implementação de MyTrait para o tipo que eventualmente é retornado como um TokenStream com o em método.

Para usar a macro, você precisará importar a macro do módulo em que a declarou:

// assumindo que você declarou a macro em um módulo my_macro_module

usar my_macro_module:: my_derive_macro;

Ao declarar a struct ou enum que usa a macro, você adicionará o #[derive (MyTrait)] atributo ao topo da declaração.

#[derive (MyTrait)]
estruturaMinhaEstrutura {
// campos aqui
}

A declaração struct com o atributo se expande para uma implementação do MyTrait traço para a estrutura:

implicar MyTrait para MinhaEstrutura {
// implementação aqui
}

A implementação permite que você use métodos no MyTrait traço em MinhaEstrutura instâncias.

Macros de atributos

Macros de atributo são macros que você pode aplicar a itens Rust como structs, enums, funções e módulos. As macros de atributo assumem a forma de um atributo seguido por uma lista de argumentos. A macro analisa o argumento para gerar o código Rust.

Você usará macros de atributo para adicionar comportamentos personalizados e anotações ao seu código.

Aqui está uma macro de atributo que adiciona um atributo personalizado a uma estrutura Rust:

// importando módulos para a definição de macro
usar proc_macro:: TokenStream;
usar citação:: citação;
usar syn::{parse_macro_input, DeriveInput, AttributeArgs};

#[proc_macro_attribute]
barfnmy_attribute_macro(attr: TokenStream, item: TokenStream) -> TokenStream {
deixar args = parse_macro_input!(attr como AtributoArgs);
deixar input = parse_macro_input!(item como DeriveInput);
deixar nome = &input.ident;

deixar gen = citação! {
#entrada
implicar #nome {
// comportamento personalizado aqui
}
};

gen.into()
}

A macro recebe uma lista de argumentos e uma definição de estrutura e gera uma estrutura modificada com o comportamento personalizado definido.

A macro recebe dois argumentos como entrada: o atributo aplicado à macro (analisado com o parse_macro_input! macro) e o item (analisado com o parse_macro_input! macro). A macro usa o citar! macro para gerar o código, incluindo o item de entrada original e um adicional implicar bloco que define o comportamento personalizado.

Finalmente, a função retorna o código gerado como um TokenStream com o em() método.

Regras de macro

As regras de macro são o tipo de macro mais direto e flexível. As regras de macro permitem que você defina a sintaxe personalizada que se expande para o código Rust no tempo de compilação. As regras de macro definem macros personalizadas que correspondem a qualquer expressão ou instrução de ferrugem.

Você usará regras de macro para gerar código clichê para abstrair detalhes de baixo nível.

Veja como você pode definir e usar regras de macro em seus programas Rust:

macro_regras! make_vector {
($($x: expr),*) => {
{
deixarmudo v = vec::novo();
$(
v.push($x);
)*
v
}
};
}

fnprincipal() {
deixar v = make_vector![1, 2, 3];
println!("{:?}", v); // imprime "[1, 2, 3]"
}

O programa define um make_vector! uma macro que cria um novo vetor a partir de uma lista de expressões separadas por vírgulas no principal função.

Dentro da macro, a definição do padrão corresponde aos argumentos passados ​​para a macro. O $($x: expr),* a sintaxe corresponde a qualquer expressão separada por vírgula identificada como $ x.

O $( ) sintaxe no código de expansão itera sobre cada expressão na lista de argumentos passados ​​para a macro após o parêntese de fechamento, indicando que as iterações devem continuar até que a macro processe todos os expressões.

Organize seus projetos Rust com eficiência

As macros Rust melhoram a organização do código, permitindo que você defina abstrações e padrões de código reutilizáveis. As macros podem ajudá-lo a escrever um código mais conciso e expressivo sem duplicações em várias partes do projeto.

Além disso, você pode organizar programas Rust em caixas e módulos para melhor organização de código, reutilização e interoperação com outras caixas e módulos.