用 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& ...
随机推荐
- 代码随想录算法训练营Day45 动态规划
代码随想录算法训练营 代码随想录算法训练营Day45 动态规划|70. 爬楼梯(进阶) 322. 零钱兑换 70. 爬楼梯 (进阶) 题目链接:70. 爬楼梯 (进阶 假设你正在爬楼梯.需要 n 阶你 ...
- shell脚本中特殊筛选文件
问题描述:在写shell中,总会遇到一些各式各样筛选文件的需求,整理了一些特殊情况 1.查找目标文件下大于100Mb的文件 find $target_dir -type f -size +70M 2. ...
- MySQL中字符串查询效率大比拼
背景 最近有个同事对字符串加索引,加完后,发现多了个奇奇怪怪的数字执行的SQL如下: alter table string_index_test add index `idx_name` (`name ...
- Spring 核心概念之一 IoC
前言 欢迎来到本篇文章!通过上一篇什么是 Spring?为什么学它?的学习,我们知道了 Spring 的基本概念,知道什么是 Spring,以及为什么学习 Spring.今天,这篇就来说说 Sprin ...
- 面向生信分析的高性 RStudio 服务器
因需要超大内存的拼接/比对/表达量计算发愁? 为了使用组里的服务器而被困在实验室? 浪费大量的时间龟速下载 NCBI 的数据? 快来看看云筏 HPC 吧! https://my.cloudraft.c ...
- substrate 编译出错unresolved import `sp_runtime::testing` failed to resolve: could not find `GenesisConfig` in `system`
error[E0432]: unresolved import `sp_runtime::testing` --> /Users/suyinrong/bitcoin-proj/substrate ...
- WPF入门教程系列二十八 ——DataGrid使用示例MVVM模式(6)
WPF入门教程系列目录 WPF入门教程系列二--Application介绍 WPF入门教程系列三--Application介绍(续) WPF入门教程系列四--Dispatcher介绍 WPF入门教程系 ...
- SpringBoot+MyBatisPlus实现读写分离
前言 随着业务量的不断增长,数据库的读写压力也越来越大.为了解决这个问题,我们可以采用读写分离的方案来分担数据库的读写负载.本文将介绍如何使用 Spring Boot + MyBatis Plus + ...
- 7. 特殊SQL的执行
1. 模糊查询 演示代码: /** * 测试模糊查询 * @param mohu * @return */ List<User> testMohu(@Param("mohu& ...
- uniapp-chatgpt跨端仿ChatGPT实例|uniapp+vue3+pinia多端聊天模板
基于uniapp+vite4+pinia跨多端实现chatgpt会话模板Uniapp-ChatGPT. uni-chatgpt 使用uni-app+vite4+vue3+pinia+uview-plu ...