• 用 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!

  • 相关阅读:
    SpringBoot-02-springBoot介绍及程序创建
    Vue+element 商品分类业务实现
    boot issue
    go-zero环境搭建
    SD卡使用记录
    python requests爬虫 POST无参请求一直在请求中,没有响应的原因
    论文阅读笔记 | 三维目标检测——SECOND算法
    广和通正式发布工业级低功耗单频双模GNSS模组G030&G031
    【Python基础】面向对象封装 案例(一)
    【Unity3D】视图中心 ( 视图中心概念 | 围绕游戏物体旋转 | 添加游戏物体到游戏场景的位置 )
  • 原文地址:https://www.cnblogs.com/hangj/p/17695966.html