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

  • 相关阅读:
    华为认证HCIA H12-811 Datacom数通考试真题题库【带答案刷题必过】【第四部分】
    (附源码)spring boot高校社团管理系统 毕业设计 231128
    windows和linux可以共用的端口连通性是否丢包测试工具paping
    vue基础语法(上)
    ElementUI之CUD+表单验证
    安卓常见设计模式3.2------工厂模式,工厂方法模式,抽象工厂模式对比(Kotlin版)
    大数据挖掘企业服务平台-道路运输安全大数据分析解决方案
    CHAPTER 4: DESIGN A RATE LIMITER
    上周热点回顾(8.7-8.13)
    anroid html5 拍照扫码
  • 原文地址:https://www.cnblogs.com/hangj/p/17695966.html