用 Rust 的 declarative macro 做了个小东西

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

解决什么问题

rust 复制代码
#[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 就是专门来干这个活儿的

简单用法

rust 复制代码
custom_meta_struct! {
	(
    	#[derive(Debug)]
        #[derive(Clone)]
    ),

    struct A;

    #[derive(Copy)]
    struct B;
}

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

rust 复制代码
#[derive(Debug)]
#[derive(Clone)]
struct A;

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

复杂点的用法

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

首先定一个 trait

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

然后让所有的 Action 都

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

具体解法:

rust 复制代码
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 内函数重载的

接下来看具体实现:

rust 复制代码
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 也是一个宏:

rust 复制代码
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`");
    };
}

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

完整示例代码

rust 复制代码
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;
}

被展开后:

rust 复制代码
#[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!

相关推荐
SomeB1oody13 小时前
【RustyML入门】4.2. 标准化与归一化
开发语言·后端·机器学习·rust·教程
leoZ23121 小时前
Vue3 还原一个企业级后台-14-项目总结
开发语言·人工智能·后端·opencv·计算机视觉·数据挖掘·rust
特立独行的猫a1 天前
Tauri v2的Rust应用 → HarmonyOS(鸿蒙 PC)移植30分钟速成指南
开发语言·rust·harmonyos·tauri·移植·鸿蒙pc
程序员爱钓鱼1 天前
Rust 方法 Method详解:self、方法调用与API设计
后端·面试·rust
Patrick_Wilson2 天前
sccache 用在 Rust 上为什么常「不省编译」:原理、限制与 Windows 接入
ci/cd·rust·编译器
k4m7v2pz2 天前
rvs 26.8.43 → 26.8.53 更新概览:MCP 落地、审计增强与谬误澄清
rust·repl·mcp server·rust-verb-shell·rvs·shell 工具
程序员爱钓鱼2 天前
Rust impl详解:为Struct定义方法与关联函数
前端·后端·rust
k4m7v2pz2 天前
Rust 程序在 ReactOS 上“加载即崩“:从崩溃地址 0x6107104d 到 CRT 依赖的深度排查
rust·交叉编译·逆向工程·reactos·pe格式·windows兼容
Thneonl3 天前
一个人从 Rust 内核做到 React 前端:我做了一个云原生 SRE 巡检图谱桌面工具
rust
程序员爱钓鱼3 天前
Rust Struct结构体详解:定义自己的复杂数据类型
后端·面试·rust