最近几天在弄 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 做了个小东西的更多相关文章

  1. FMX相当于在界面上自己又做了一个小操作系统

    FMX的自画界面我也不看好,比如复制粘贴,太丑了,系统做得很好很精细的复制粘贴界面,就是无法调出,比如MIUI,复制粘贴还能有个放大镜,可以选择到屏幕边缘的文字,可以选择剪贴板内多个可粘贴的文字:还有 ...

  2. 又见angular----步一步做一个angular4小项目

    这两天看了看angular4的文档,发现他和angular1.X的差别真的是太大了,官方给出的那个管理英雄的Demo是一个非常好的入门项目,这里给出一个管理个人计划的小项目,从头至尾一步一步讲解如何去 ...

  3. WPF做验证码,小部分修改原作者内容

    原文地址:http://www.cnblogs.com/tianguook/p/4142346.html 首先感谢aparche大牛的帖子,因为过两天可能要做个登录的页面,因此,需要用到验证码,从而看 ...

  4. [第一个自己做的C小程序]丧失求生文字小游戏

    丧失求生文字小游戏 编写原因: 我编写这个小程序是为了结合下我学习的知识并且做一个小游戏来看看我自己的能力,目前我已经学完了C语言的编程基础.马上就要学到指针,这个就是我的基础总结项目,希望大家可以都 ...

  5. 如何为你的美术妹子做Unity的小工具(二)

    你想像这样一样  为自己的Unity 小工具打开一个Unity的窗口吗?   看起来就很厉害对不对   妹子看了还不激动吗 ?!

  6. 如何为你的美术妹子做Unity的小工具(一)

    在上的工具栏添加   也就是这个位置

  7. 突发奇想想学习做一个HTML5小游戏

    前言: 最近一期文化馆轮到我分享了,分享了两个,一个是关于童年教科书的回忆,一个是关于免费电子书的.最后我觉得应该会不敌web,只能说是自己在这中间回忆了一下那个只是会学习的年代,那个充满梦想的年代. ...

  8. 用css3做一个求婚小动画

    概述 本案例主要是运用到了css3的animation.keyframes.transform等属性,熟悉了,就可以做更多的其他动画效果,这几个属性功能非常强大. 详细 代码下载:http://www ...

  9. 自己工作之余做的OSX小软件

    ShareSDK是为iOS.Android.WindowsPhone提供社会功能的一个组件,开发者只需10分钟即可集成到自己的APP中,它不仅支持分享给QQ好友.微信好友.微信朋友圈.新浪微博.腾迅微 ...

  10. css3+jquery+js做的翻翻乐小游戏

    主要是为了练习一下css3的3D翻转功能,就做了这么个小游戏,做的比较粗糙,但是效果看的见. 主要用到的css3代码如下: html结构: <div class="container& ...

随机推荐

  1. odoo部署安全性问题

    本文档描述在生产中或在面向Internet的服务器上设置Odoo的基本步骤.它是在安装之后进行的,对于没有在internet上公开的开发系统来说,它通常不是必需的.警告如果您正在设置公共服务器,请务必 ...

  2. 百度飞桨(PaddlePaddle) - PP-OCRv3 文字检测识别系统 基于 Paddle Serving快速使用(服务化部署 - CentOS 7)

    目录 Paddle Serving服务化部署实战 准备预测数据和部署环境 环境准备 安装 PaddlePaddle 2.0 安装 PaddleOCR 准备PaddleServing的运行环境, 模型转 ...

  3. Spring Cloud开发实践(七): 集成Consul配置中心

    目录 Spring Cloud开发实践(一): 简介和根模块 Spring Cloud开发实践(二): Eureka服务和接口定义 Spring Cloud开发实践(三): 接口实现和下游调用 Spr ...

  4. 信息收集_网络扫描_nmap

    信息收集_网络扫描nmap 目标说明 -iL <inputname> (从列表或文件输入) -iR <hostnum> (随机选择生成目标数量) --exclude <h ...

  5. Swift4 入门到精通(第二章基本数据类型与量值)

    第二章 量值和基本数据类型 Swift 支持的基本数据类型, 整型,浮点型,布尔型,元组,可选类型. 学习的目标: 常量与变量的意义.声明.命名规范.类型 数据进制与计算机存储原理 整型数据.浮点型数 ...

  6. 【快应用】addEventListener()方法无法监听动画事件

    ​[关键词] 动画监听.动态改变 [问题背景] Style中设置动画样式,然后在onshow生命周期中调用addEventListener()方法去监听动画事件,无法监听到,该方法无任何回调返回 问题 ...

  7. 【Linux内核】内核源码编译

    Linux内核源码编译过程 总体流程: 下载Linux内核源码文件 安装所需工具 解压源码文件并配置 make编译源码 下载busybox 配置busybox并编译 1. Linux源码编译 http ...

  8. 如何使用idea来查找所有未使用的代码?

    背景 项目组需要对开发的项目进行一次清理,把一些未被引用的代码清理掉. 我们知道一段代码未被引用,那么代码通常是灰色的. 但是一个完整的项目,会存在成千上万个Java文件,如果一个一个看去需要花费太多 ...

  9. ARC114F Permutation Division

    题意 给定一个 \(1 \sim N\) 的排列,Alice 把它划分成 \(k\) 段,Bob 把这 \(k\) 段任意排列.Alice 想让字典序最小,Bob 想让字典序最大.请问最后的排列. 数 ...

  10. go NewTicker 得使用

    转载请注明出处: 在 Go 语言中,time.NewTicker 函数用于创建一个周期性触发的定时器.它会返回一个 time.Ticker 类型的值,该值包含一个通道 C,定时器会每隔一段时间向通道 ...