用 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& ...
随机推荐
- C++面试八股文:指针占用多少个字节?
某日小二参加XXX科技公司的C++工程师开发岗位4面: 面试官:memset.memcpy和strcpy的区别是什么? 小二:memset用于将一块内存设置为特定的值, memcpy用于将一块内存从一 ...
- 适用于Linux命令的10个R函数
由于微信不允许外部链接,你需要点击文章尾部左下角的 "阅读原文",才能访问文中链接. 这篇文章将介绍 10 个不同的 Linux 命令及其 R 实现方法. 如果您有兴趣学习更多 R ...
- Collection 接口及其常用方法
Collection 接口的特点 Collection接口没有直接实现类,提供了更具体的子接口(如Set和List)的实现.Collection实现类(通常通过其中一个子接口间接实现Collectio ...
- JAVA生成xml文件格式
摘要: 1.在某些业务中需要使用JAVA按照规定生成固定XML格式文件,本文中根据相应的业务生成固定的XML格式文件,并且通过测试可以发送. 2.下面代码根绝dom4j生成xml格式文件 代码思路: ...
- 根据模板动态生成word(三)使用poi-tl生成word
@ 目录 一.前言 1.什么是poi-tl 2.官方信息 2.1 源码仓库 2.2 中文文档 2.3 开源协议 3.poi-tl的优势 3.1 poi-tl和其他模板引擎的对比 3.2 poi-tl ...
- animation动画+关键帧实现轮播图效果(再次学习)!
再次遇到要实现轮播图效果的时候,发现还是不怎么会,因为对js还没有熟练使用,只希望使用h5和css3实现效果 虽然之前已经学习了一遍了,但是还是不熟练,再次学习一下了 这次的可作为套板使用,无序列表为 ...
- tensorflow神经网络归一化方法
参考https://blog.csdn.net/chary8088/article/details/81542879
- Asp.Net MVC中Action跳转小结(转载)
来源: https://www.cnblogs.com/surfing/p/3542826.html 首先我觉得action的跳转大致可以这样归一下类,跳转到同一控制器内的action和不同控制器内的 ...
- 【译】Visual Studio 2022 中的 Web API 开发
在 Visual Studio 2022 中,Web 开发人员的主要场景之一是使用 ASP.NET Core 创建 Web API.在 Visual Studio 2022 17.6 的最新预览版中, ...
- TypeScript: 类型别名
类型别名 在 TYPESCRIPT 中,类型别名可以使用 TYPE 关键字来定义.类型别名可以方便地定义一个类型,并为其起一个易于理解的名称,以便在其他地方引用该类型时使用. 示例 type MySt ...