Кто-нибудь помнит, сколько ям мы выкопали вместе? Хм... На самом деле я сам не помню.Сегодня мы будем копать специальную яму.Эту яму можно сказать выкопали под корень-метапрограммирование.
Метапрограммирование — важная концепция в области программирования, которая позволяет программам воспринимать код как данные и изменять или заменять код во время выполнения. Если вы знакомы с Java, задумывались ли вы о механизме отражения в Java? Да, это тип метапрограммирования.
отражение
Rust также поддерживает отражение, которое предоставляется стандартной библиотекой Rust.std::any::Any
пакет поддерживается.
В этом пакете представлены следующие методы
TypeId — это тип в Rust, который используется для представления уникального идентификатора типа.type_id(&self)
Этот метод возвращает TypeId переменной.
is()
Методы используются для определения типа функции.
Вы можете взглянуть на его реализацию исходного кода
pub fn is<T: Any>(&self) -> bool {
let t = TypeId::of::<T>();
let concrete = self.type_id();
t == concrete
}
Вы можете видеть, что его реализация очень проста, то есть сравнивать TypeId.
downcast_ref()
а такжеdowncast_mut()
— это пара методов для преобразования универсального типа T в конкретный тип. Его возвращаемый типOption<&T>
а такжеOption<&mut T>
, то естьdowncast_ref()
преобразует тип T в неизменяемую ссылку, аdowncast_mut()
Преобразуйте T в изменяемую ссылку.
Наконец, давайте рассмотрим конкретное использование этих функций на примере.
use std::any::{Any, TypeId};
fn main() {
let v1 = "Jackey";
let mut a: &Any;
a = &v1;
println!("{:?}", a.type_id());
assert!(a.is::<&str>());
print_any(&v1);
let v2: u32 = 33;
print_any(&v2);
}
fn print_any(any: &Any) {
if let Some(v) = any.downcast_ref::<u32>() {
println!("u32 {:x}", v);
} else if let Some(v) = any.downcast_ref::<&str>() {
println!("str {:?}", v);
} else {
println!("else");
}
}
макрос
Механизм отражения Rust предоставляет ограниченную функциональность, но Rust также предоставляет макросы для поддержки метапрограммирования.
До сих пор макросы — это понятие, которое нам знакомо и незнакомо, знакомо, потому что мы использовалиprintln!
макрос, странный, потому что мы никогда не освещали его подробно.
заprintln!
Макросы мы интуитивно чувствуем, что они похожи на функции. Но между ними есть некоторые различия.
Мы знаем, что для функции число получаемых параметров фиксировано, и оно фиксируется при определении функции. Количество параметров, получаемых макросом, не фиксировано.
Все макросы, о которых мы здесь говорим, являются макросами, подобными функциям.Кроме того, в Rust также есть макрос, который является макросом, подобным свойствам. Это чем-то похоже на аннотацию в Java и обычно пишется как своего рода разметка над именем функции.
#[route(GET, "/")]
fn index() {
Маршрут используется здесь для указания метода интерфейса.Для этой службы корневой путьGET
Запросы направляются в эту индексную функцию. Такие макросы создаются принадлежностью кпроцедурный макрос, который определяется с помощью#[proc_macro_attribute]
аннотация. И процедурные макросы, подобные функциям, используют аннотации, когда они определены как#[proc_macro]
.
В дополнение к процедурным макросам существует еще одна большая категория макросов.объявить макрос. Объявление макросов выполняется черезmacro_rules!
для объявления определенных макросов, которые используются более широко, чем процедурные макросы. мы были в контакте сvec!
Это тип макроса объявления. Он определяется следующим образом:
#[macro_export]
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
Давайте определим наш собственный макрос.
Необходимо использовать пользовательские макросыderive
аннотация. (пример из книги)
Давайте сначала создадим библиотеку lib с именем hello_macro и определим только один трейт.
pub trait HelloMacro {
fn hello_macro();
}
Затем создайте подкаталог hello_macro_derive и добавьте зависимости в файл hello_macro_derive/Cargo.toml.
[lib]
proc-macro = true
[dependencies]
syn = "0.14.4"
quote = "0.6.3"
Затем мы можем определить функцию нашего пользовательского макроса в файле hello_macro_derive/lib.rs.
extern crate proc_macro;
use crate::proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_hello_macro(&ast)
}
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}", stringify!(#name));
}
}
};
gen.into()
}
Здесь используются два крейта: syn и quote, где syn преобразует код Rust в специальную операционную структуру данных, а quote делает прямо противоположное.
Как видите, аннотации, используемые нашим пользовательским макросом,#[proc_macro_derive(HelloMacro)]
, где HelloMacro — имя макроса, при использовании нам достаточно использовать аннотацию#[derive(HelloMacro)]
Вот и все.
При использовании мы должны сначала ввести эти две зависимости
hello_macro = { path = "../hello_macro" }
hello_macro_derive = { path = "../hello_macro/hello_macro_derive" }
а затем использовать
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;
#[derive(HelloMacro)]
struct Pancakes;
fn main() {
Pancakes::hello_macro();
}
Текущий результат показывает, что мы можем успешно зафиксировать имя структуры в реализации.
Суммировать
В этой статье мы представили два метапрограммирования Rust: отражение и макросы. Среди них функция, обеспечиваемая отражением, слабая, но функция, обеспечиваемая макросом, очень мощная. Знания о макросах, которые мы представили, на самом деле всего лишь оболочка.Чтобы по-настоящему понять макросы, требуется больше времени для изучения.