用 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 做了个小东西的更多相关文章
- FMX相当于在界面上自己又做了一个小操作系统
FMX的自画界面我也不看好,比如复制粘贴,太丑了,系统做得很好很精细的复制粘贴界面,就是无法调出,比如MIUI,复制粘贴还能有个放大镜,可以选择到屏幕边缘的文字,可以选择剪贴板内多个可粘贴的文字:还有 ...
- 又见angular----步一步做一个angular4小项目
这两天看了看angular4的文档,发现他和angular1.X的差别真的是太大了,官方给出的那个管理英雄的Demo是一个非常好的入门项目,这里给出一个管理个人计划的小项目,从头至尾一步一步讲解如何去 ...
- WPF做验证码,小部分修改原作者内容
原文地址:http://www.cnblogs.com/tianguook/p/4142346.html 首先感谢aparche大牛的帖子,因为过两天可能要做个登录的页面,因此,需要用到验证码,从而看 ...
- [第一个自己做的C小程序]丧失求生文字小游戏
丧失求生文字小游戏 编写原因: 我编写这个小程序是为了结合下我学习的知识并且做一个小游戏来看看我自己的能力,目前我已经学完了C语言的编程基础.马上就要学到指针,这个就是我的基础总结项目,希望大家可以都 ...
- 如何为你的美术妹子做Unity的小工具(二)
你想像这样一样 为自己的Unity 小工具打开一个Unity的窗口吗? 看起来就很厉害对不对 妹子看了还不激动吗 ?!
- 如何为你的美术妹子做Unity的小工具(一)
在上的工具栏添加 也就是这个位置
- 突发奇想想学习做一个HTML5小游戏
前言: 最近一期文化馆轮到我分享了,分享了两个,一个是关于童年教科书的回忆,一个是关于免费电子书的.最后我觉得应该会不敌web,只能说是自己在这中间回忆了一下那个只是会学习的年代,那个充满梦想的年代. ...
- 用css3做一个求婚小动画
概述 本案例主要是运用到了css3的animation.keyframes.transform等属性,熟悉了,就可以做更多的其他动画效果,这几个属性功能非常强大. 详细 代码下载:http://www ...
- 自己工作之余做的OSX小软件
ShareSDK是为iOS.Android.WindowsPhone提供社会功能的一个组件,开发者只需10分钟即可集成到自己的APP中,它不仅支持分享给QQ好友.微信好友.微信朋友圈.新浪微博.腾迅微 ...
- css3+jquery+js做的翻翻乐小游戏
主要是为了练习一下css3的3D翻转功能,就做了这么个小游戏,做的比较粗糙,但是效果看的见. 主要用到的css3代码如下: html结构: <div class="container& ...
随机推荐
- 【技术分享】万字长文图文并茂读懂高性能无锁 “B-Tree 改”:Bw-Tree
[技术分享]万字长文图文并茂读懂高性能无锁 "B-Tree 改":Bw-Tree 原文链接: https://mp.weixin.qq.com/s/I5TphQP__tHn6JoP ...
- 如何使用 Megatron-LM 训练语言模型
在 PyTorch 中训练大语言模型不仅仅是写一个训练循环这么简单.我们通常需要将模型分布在多个设备上,并使用许多优化技术以实现稳定高效的训练.Hugging Face Accelerate 的创建 ...
- 2023最新IntellJ IDEA诺依SpringCloud开发部署文档(保姆级别)
目录 若依RuoYi v3.6.2部署文档 一.环境构建 二.模块描述 三.部署后端 1.下载到本地. 2.MySQL导入数据. 3.Nacos修改 (1)保证本地Nacos下载安装成功,修改本地Na ...
- 20230611 再次升级SSD
家里常用电脑的硬盘又显得捉襟见肘,老规矩,升级SSD.幸亏几年前摸索的方法记录下来了,翻出以前的博客复习一下.为了保险起见,也重新在网上搜了一下,看是不是有新的更方便的方法,答案是没有,只是搜出很多推 ...
- 无限分解流----Fork/Join框架
Fork译为拆分,Join译为合并Fork/Join框架的思路是把一个非常巨大的任务,拆分成若然的小任务,再由小任务继续拆解.直至达到一个相对合理的任务粒度.然后执行获得结果,然后将这些小任务的结果汇 ...
- 重新搞懂Git,掌握日常命令和基本操作
1.git Git 是一个免费的开源分布式版本控制系统,旨在快速高效地处理从小型到超大型项目的所有内容. Git 易于学习,占用空间很小,性能快如闪电.它超越了Subversion,CVS,Perfo ...
- Eclipse Alt + / 无提示
步骤 一: Widows - Preference - Java - Editor - Content Assist - Advanced 勾选 Java Proposals 二: 在这个位置 点的后 ...
- java开发的配置文件配置到数据库(配置到配置文件里面个人感觉修改较麻烦,故配置到数据库)
配置文件的创建表sql CREATE TABLE `checkwork_tab_properties` ( `id` varchar(50) NOT NULL COMMENT '主键id', `typ ...
- SDK 接入|游戏语音之“范围语音”接入实践
语音是线上游戏用户的主要交流方式,大多数用户会通过游戏中的内置语音功能与其他玩家沟通,而一些用户在游戏没有内置语音功能的情况下,通过其他语音软件与玩家沟通. 并且,游戏语音在玩家开黑时承担着至关重要的 ...
- iphone拍照的历史顽固问题-鬼影
iphone11 系列的鬼影问题 近期苹果 iPhone 11 系列的手机又出现了新问题,其中有不少网友表示,自己在用手机拍照后,图片中莫名出现了"鬼影"的现象,这次的" ...