需求:
獲取枚舉類型附加信息,如備注說明抬闯,傳統(tǒng)作法如下:
實現(xiàn) comment 方法根據(jù)匹配值返回對應字符串
enum Robot {
Alex,
Bob
}
impl Robot {
fn comment(&self) -> &'static str {
match self {
Robot::Alex => "這是Alex",
Robot::Bob => "這是Bob",
}
}
}
// test
println!("alex comment: {}", Robot::Alex.comment()); // alex comment: 這是Alex
傳統(tǒng)作法有一個問題,如果有很多枚舉关筒,就需要各自實現(xiàn)對應的 comment 方法實現(xiàn)画髓,很麻煩,是否可以簡化呢平委?
可以的!
其中 comment 方法的實現(xiàn)就屬于模板代碼夺谁。這種重復性的工作就可以考慮使用宏廉赔,最終簡化如下:
除了 comment 也可以附加 其它信息如 姓名、年齡等
#[derive(HelloMacro)]
enum Robot {
#[oic_column(name = "alex_name", age = 22, comment = "這是Alex")]
Alex,
#[oic_column(name = "bob_name", age = 50, comment = "這是Bob")]
Bob,
}
// 測試可以獲取一樣的結(jié)果
println!("alex comment: {}", Robot::Alex.comment()); // alex comment: 這是Alex
什么是過程宏
過程宏中文文檔參考: https://zjp-cn.github.io/rust-note/proc/quote.html
過程宏(Procedure Macro)
是Rust中的一種特殊形式的宏匾鸥,提供比普通宏更強大的功能蜡塌。過程宏主要分三類:
- 派生宏(Derive macro):用于結(jié)構(gòu)體(struct)、枚舉(enum)勿负、聯(lián)合(union)類型馏艾,可為其實現(xiàn)函數(shù)或特征(Trait)。
- 屬性宏(Attribute macro):用在結(jié)構(gòu)體奴愉、字段琅摩、函數(shù)等地方,為其指定屬性等功能锭硼。如標準庫中的#[inline]房资、#[derive(...)]等都是屬性宏。
- 函數(shù)式宏(Function-like macro):用法與普通的規(guī)則宏類似檀头,但功能更加強大轰异,可實現(xiàn)任意語法樹層面的轉(zhuǎn)換功能。
1.派生宏示例:
#[proc_macro_derive(Builder)]
fn derive_builder(input: TokenStream) -> TokenStream {
let _ = input;
unimplemented!()
}
其使用方法如下:
#[derive(Builder)]
struct Command {
// ...
}
2.屬性宏示例
#[proc_macro_attribute]
fn sorted(args: TokenStream, input: TokenStream) -> TokenStream {
let _ = args;
let _ = input;
unimplemented!()
}
其使用方法如下:
#[sorted]
enum Letter {
A,
B,
C,
// ...
}
3.函數(shù)式宏示例
#[proc_macro]
pub fn seq(input: TokenStream) -> TokenStream {
let _ = input;
unimplemented!()
}
其使用方法如下:
seq! { n in 0..10 {
/* ... */
}}
功能實現(xiàn)
本例主要使用派生宏實現(xiàn)
項目創(chuàng)建可以參數(shù)示例倉庫https://github.com/imbolc/rust-derive-macro-guide
第一步 創(chuàng)建測試項目
測試項目: my-macro-test
宏項目: my-derives
項目結(jié)構(gòu)最終如下:
my-macro-test/
my-derives/ // 子項目
src/
attributes.rs
lib.rs
Cargo.toml
src/
main.rs // 主項目入口
Cargo.toml
文件 my-macro-test/Cargo.toml
# my-macro-test/Cargo.toml
[package]
name = "my-macro-test"
version = "0.1.0"
edition = "2021"
[dependencies]
# 根據(jù)路徑指定子項目
my_derives = { path = "my-derives" }
文件 my-macro-test/my-derives/Cargo.toml
[package]
# my-macro-test/my-derives/Cargo.toml
name = "my-derives"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
syn = { version = "^1", features = ["full"] }
quote = "^1"
proc-macro2 = "^1"
bae = "^0"
重要的第三方庫:
- quote 把 Rust 語法樹的數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)化為源代碼的標記 (tokens)
- syn 主要是一個解析庫暑始,用于把 Rust 標記流解析為 Rust 源代碼的語法樹
- bae 簡化屬性數(shù)據(jù)的處理
第二步 自定義屬性定義
文件: my-derives/attributes.rs
// my-derives/attributes.rs
use bae::FromAttributes;
use syn;
#[derive(Default, FromAttributes, Debug)]
pub struct OicColumn {
pub name: Option<syn::Lit>,
pub age: Option<syn::Lit>,
pub comment: Option<syn::Lit>,
}
第三步 HelloMacro 派生宏實現(xiàn)
文件: my-derives/lib.rs
mod attributes;
use proc_macro::{self, TokenStream};
use quote::{quote};
use syn::{parse_macro_input, DeriveInput};
use attributes::{OicColumn};
// HelloMacro 定義
#[proc_macro_derive(HelloMacro, attributes(oic_column))]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
let input: DeriveInput = parse_macro_input!(input);
// ident 當前枚舉名稱
let DeriveInput { ident, .. } = input;
let mut comment_arms = Vec::new();
if let syn::Data::Enum(syn::DataEnum { variants, .. }) = input.data {
for variant in variants {
// 當前枚舉項名稱如 Alex, Box
let ident_item = &variant.ident;
// 根據(jù)屬性值轉(zhuǎn)為 OicColumn 定義的結(jié)構(gòu)化數(shù)據(jù)
if let Ok(column) = OicColumn::from_attributes(&variant.attrs) {
// 獲取屬性中的comment信息
let msg: &syn::Lit = &column.comment.unwrap();
// 生成 match 匹配項 Robot::Alex => "msg"
comment_arms.push(quote! ( #ident::#ident_item => #msg ));
} else {
comment_arms.push(quote! ( #ident::#ident_item => "" ));
}
}
}
if comment_arms.is_empty() {
comment_arms.push(quote! ( _ => "" ));
}
// 實現(xiàn) comment 方法
let output = quote! {
impl #ident {
fn comment(&self) -> &'static str {
match self {
#(#comment_arms),*
}
}
}
};
output.into()
}
#(#comment_arms),*
為數(shù)據(jù)解構(gòu)語法搭独,具體參考:https://docs.rs/quote/1.0.21/quote/macro.quote.html
第四步 測試代碼實現(xiàn)
文件: my-macro-test/main.rs
use my_derives::HelloMacro;
#[derive(HelloMacro)]
enum Robot {
#[oic_column(name = "alex_name", age = 22, comment = "這是Alex")]
Alex,
#[oic_column(name = "bob_name", age = 50, comment = "這是Bob")]
Bob,
Apple,
}
fn main() {
// test comment: Alex: "這是Alex", ----- Bob: "這是Bob"
println!("test comment: Alex: {:?}, ----- Bob: {:?}", Robot::Alex.comment(), Robot::Bob.comment());
// test comment apple: ""
println!("test comment apple: {:?}", Robot::Apple.comment());
}
cargo run 運行測試輸出結(jié)果如注釋部分。