用 Rust 的 declarative macro 做了个小东西

最近几天在弄 ddnspod 的时候,写了个宏: custom_meta_struct

解决什么问题

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] struct ActionA {     url: String, // https://example.com     version: String, // v1.2.3     a: u64,     // ... }  #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[some custome attribute] // 这个 action 独有 attribute struct ActionB {     url: String, // https://example.com     version: String, // v1.2.3     b: bool,     // ... }  // 后面很多的 Action  // ... 

上面代码中有很多个 struct Action

每一个 Action 都有一些像 #[derive(Debug)] 这样的共同的 Attributes

每个 struct 内同样也都有像 url version 这样相同的 fields

并且大部分的值都相同, 此时我该如何利用 macro 来减少重复代码的编写?

custom_meta_struct! {}

我的 custom_meta_struct 就是专门来干这个活儿的

简单用法

custom_meta_struct! { 	(     	#[derive(Debug)]         #[derive(Clone)]     ),      struct A;      #[derive(Copy)]     struct B; } 

这段代码展开后会变成这样:

#[derive(Debug)] #[derive(Clone)] struct A;  #[derive(Debug)] #[derive(Clone)] #[derive(Copy)] struct B; 

复杂点的用法

对于 url version 也避免重复的用法:

首先定一个 trait

trait CommonParams { 	fn url(&self) -> String { "https://hangj.cnblogs.com" }     fn version(&self) -> String { "v1.2.3".into() } } 

然后让所有的 Action 都

impl CommonParams for ActionX { 	// 如果这个 Action 的 url 或 version 比较特殊, 就重载一下 } 

具体解法:

custom_meta_struct! { 	(     	define_structs, // callback macro         #[derive(Debug)]     ),      #[derive(Clone)]     struct A;  	@[version = "v2.3.4".into()]     #[derive(serde::Serialize)]     struct B;      @[url = "https://crates.io/crates/ddnspod".into()]     struct C; } 

其中的 define_structs 也是一个宏, 用来作为回调, custom_meta_struct 会对将要展开的代码做一个格式化, 代码格式化之后传递给 define_structs

@[..] 是我们的自定义属性, 用来辅助实现 trait CommonParams 内函数重载的

接下来看具体实现:

macro_rules! define_structs { 	(     	$(         	$(#[$meta: meta])*             $(@[$($my_meta: tt)*])*             $vis: vis struct $name: ident $body: tt         )*     ) => {     	$(             $(#[$meta])*             $vis struct $name $body              impl CommonParams for $name {                 $(                 	overriding_method!( $($my_meta)* );                 )*             }         )*     }; } 

overriding_method 也是一个宏:

macro_rules! overriding_method { 	(url = $expr: expr) => {     	fn url(&self) -> String { $expr }     }; 	(version = $expr: expr) => {         fn version(&self) -> String { $expr }     };     ($($tt: tt)*) => {         compile_error!("This macro only accepts `url` and `version`");     }; } 

经过这一系列操作, 就完美解决了最前面的问题

完整示例代码

trait CommonParams { 	fn url(&self) -> String { "https://hangj.cnblogs.com" }     fn version(&self) -> String { "v1.2.3".into() } }  macro_rules! overriding_method { 	(url = $expr: expr) => {     	fn url(&self) -> String { $expr }     }; 	(version = $expr: expr) => {         fn version(&self) -> String { $expr }     };     ($($tt: tt)*) => {         compile_error!("This macro only accepts `url` and `version`");     }; }  macro_rules! define_structs { 	(     	$(         	$(#[$meta: meta])*             $(@[$($my_meta: tt)*])*             $vis: vis struct $name: ident $body: tt         )*     ) => {     	$(             $(#[$meta])*             $vis struct $name $body              impl CommonParams for $name {                 $(                 	overriding_method!{ $($my_meta)* }                 )*             }         )*     }; }  custom_meta_struct! { 	(     	define_structs, // callback macro         #[derive(Debug)]     ),      #[derive(Clone)]     struct A;  	@[version = "v2.3.4".into()]     #[derive(serde::Serialize)]     struct B;      @[url = "https://crates.io/crates/ddnspod".into()]     struct C; } 

被展开后:

#[derive(Debug)] #[derive(Clone)] struct A;  impl CommonParams for A {}  #[derive(Debug)] #[derive(serde::Serialize)] struct B;  impl CommonParams for B {     fn version(&self) -> String { "v2.3.4".into() } }  #[derive(Debug)] struct C;  impl CommonParams for C {     fn url(&self) -> String { "https://crates.io/crates/ddnspod".into() } } 

最后

custom_meta_struct 的代码有 300 行左右, 花了我好多精力

要想编写出符合预期且行为复杂的 declarative macro 还是挺有挑战性的, 但是写完之后很有成就感 ✌️✌️

如果你想了解更多细节,不妨直接看代码 https://github.com/hangj/dnspod-lib/tree/main/src/macros

Have fun!

用 Rust 的 declarative macro 做了个小东西

发表评论

评论已关闭。

相关文章

  • 0