
有沒有同學記得我們一起挖了多少個坑?嗯…其實我自己也不記得了,今天我們再來挖一個特殊的坑,這個坑可以說是挖到根源了——元程式設計
有沒有同學記得我們一起挖了多少個坑?嗯…其實我自己也不記得了,今天我們再來挖一個特殊的坑,這個坑可以說是挖到根源了——元程式設計。
元程式設計是程式設計領域的一個重要概念,它允許程式将代碼作為資料,在運作時對代碼進行修改或替換。如果你熟悉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() {
route在這裡是用來指定接口方法的,對于這個服務來講,根路徑的
GET
請求都被路由到這個index函數上。這樣的宏是通過屬于過程宏,它的定義使用了
#[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
注解。(例子來自the book)
我們先來建立一個叫做hello_macro的lib庫,隻定義一個trait。
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()
}
這裡使用了兩個crate: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的兩種元程式設計:反射和宏。其中反射提供的功能能力較弱,但是宏提供的功能非常強大。我們所介紹的宏的相關知識其實隻是皮毛,要想真正了解宏,還需要花更多的時間學習。