当前位置: 首页 > news >正文

Rust 宏的使用

两种宏的实现方式

  1. 通过标准库提供的 macro_rules! 宏实现——示范型宏
  2. 通过提供编译器扩展来实现

宏的分析工具

cargo install cargo-expand
cargo expand

通过 macro_rules! 实现

macro_rules! add {
    ($a:expr, $b:expr) => {
        $a + $b
    };
}

let x = add(1, 2);
// 宏展开后变成
let x = 1 + 2;
宏参数可重复性

* 任意次
+ 一次或者多次
? 零次或者一次

macro_rules! add {
    ( $($a:expr),* ) => {
        {
            let mut sum = 0;
            $( sum += $a; )*
            sum
        }
    };
}

let x = add!(1, 2, 3);

// 宏展开后为
let x = {
    let mut sum = 0;
    sum += 1;
    sum += 2;
    sum += 3;
    sum
};
递归调用

添加了更多的匹配规则

第一个模式匹配规则是 () ,表示没有传入任何参数时,返回值为0
第二个模式匹配规则是 ($a:expr) ,表示只传入一个表达式时,返回该表达式的值
第三个模式匹配规则是 ($a:expr, $($rest:expr),*) ,表示传入多个表达式时,将第一个表达式与剩余的表达式进行相加操作

macro_rules! add {
    () => {
        0
    };
    ($a:expr) => {
        $a
    };
    ($a:expr, $($rest:expr),*) => {
        $a + add!($($rest),*)
    };
}

let x = add!(1, 2, 3);
// 宏展开后为
let x = 1 + 2 + 3;

过程宏

格式为如下: 定义过程宏的函数将 TokenStream 作为输入 并产生 TokenStream 作为输出

use proc_macro;

#[some_attribute]
pub fn some_name(input: TokenStream) -> TokenStream {

}

分为三类

函数式宏 Function-like macros - custom!(…)
派生宏 Derive macros - #[derive(CustomDerive)]
属性宏 Attribute macros - #[CustomAttribute]

过程宏需要作为一个 crate 使用,因此可以单独新建一个工程来实现,然后在另一个工程引入

当然也可以直接在 crate 工程中调用自身的 lib, 但这会导致库的包名和目标名碰撞警告

cargo new demo
cd demo && cargo new custom_macro

custom_macro/Cargo.toml

[lib]
proc-macro = true
定义宏

custom_macro/src/lib.rs

use proc_macro::TokenStream;

#[proc_macro]
pub fn make_answer(_input: TokenStream) -> TokenStream {
    "fn answer() -> u32 { 42 }".parse().unwrap()
}
使用宏

Cargo.toml

[dependencies]
custom_macro={path="custom_macro"}

src/main.rs

use custom_macro::make_answer;

make_answer!();
// 将展开为
// fn answer() -> u32 {
//     42
// }

fn main() {
    println!("{}", answer());
}

更多示例

1. 函数式宏
// ---- custom_macro/src/lib.rs ----
use proc_macro::TokenStream;

#[proc_macro]
pub fn make_hello(input: TokenStream) -> TokenStream {
    let name = input.to_string();
    let hell = "Hello ".to_string() + name.as_ref();
    let fn_name =
        "fn hello_".to_string() + name.as_ref() + "(){ println!(\"" + hell.as_ref() + "\"); }";
    fn_name.parse().unwrap()
}

// ---- src/main.rs ----
use custom_macro::make_hello;

make_hello!(world);

fn main() {
    hello_world();
}

通过对传入的 TokenStream 进行解析,那么宏可输入的参数就可以完全自定义化,甚至自定义化为另一种语言并解析。

比如像 CSS 这类样式语法, 通过宏来解析,使得可以直接将样式写在 rust 里面

2. 派生宏

派生宏可用于 struct, trait 和 enum 类型的扩展

先通过简单示例查看派生宏定义和使用以及 TokenStream 中的内容

// ---- custom_macro/src/lib.rs ----
use proc_macro::TokenStream;

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {

    println!("{}", input.to_string());

    return TokenStream::new();
}

// ---- src/main.rs ----
use custom_macro::HelloMacro;

#[derive(HelloMacro)]
struct Person;

fn main() {
    let _ = Person;
}

编译时可以看到输出的日志 struct Person ; 说明输入的 TokenStream 正是派生宏所作用的下方结构体的定义。

那么拿到这个结构体的定义后我们就可以对其进行扩展化。

使用 syn 来解析 TokenStream 中的语法, 然后使用 quote 输出代码
// ---- custom_macro/src/lib.rs ----
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse, DeriveInput};

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {

    println!("{}", input.to_string());

    // 解析出 DeriveInput 结构体
    let ast: DeriveInput = parse(input).unwrap();

    // 为该类型实现一个函数
    let name = ast.ident;
    let gen = quote! {
        impl #name {
            fn hello_macro() {
                println!("Hello, Macro! My name is {}", stringify!(#name));
            }
        }
    };
    gen.into()
}

// ---- src/main.rs ----
use custom_macro::HelloMacro;

#[derive(HelloMacro)]
struct Person;

fn main() {
    Person::hello_macro();
}
3. 属性宏

属性宏类似于派生宏,但相比派生宏更灵活,因为你可以自定义宏属性,此外不仅可以作用于结构体枚举,还可以作用于其它项,例如函数

先通过一个简单示例了解属性宏的定义和使用

// ---- custom_macro/src/lib.rs ----
use proc_macro::TokenStream;

#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
    
    println!("attr {} item {}", attr.to_string(), item.to_string());

    TokenStream::new()
}

// ---- src/main.rs ----
use custom_macro::route;

#[route(GET, "/")]
fn index() {
}

fn main() {
}

编译输出日志 attr GET, "/" item fn index() {},可见传入的属性和宏所作用的函数项都能够在宏代码处获取。

那么拿到这些属性和函数定义本身后,我们就可以有针对性地生成代码了

// ---- custom_macro/src/lib.rs ----
use proc_macro::TokenStream;
use quote::quote;

#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
    
    println!("attr {} item {}", attr.to_string(), item.to_string());
    let name = attr.to_string();
    let gen = quote! {
        fn hello_macro() {
            println!("ATTR IS {}", stringify!(#name));
        }
    };
    gen.into()
}

// ---- src/main.rs ----
use custom_macro::route;

#[route(GET, "/")]
fn index() {
    println!("HH");
}

fn main() {
    hello_macro();
}

和派生宏不同的是属性宏的输出将会覆盖宏所作用的项, 因此再调用 index() 时将会报错,因为输出中没有定义该函数

参考文献
https://doc.rust-lang.org/reference/macros.html

相关文章:

  • 把项目转换为md知识库,提供给gpts使用
  • ffmpeg 视频分辨率修改 质量压缩
  • mysql进阶-索引基础
  • 视频转为序列图的软件,让视频批量转为序列图
  • Linux Capabilities 进阶实战
  • 单因素方差分析--R
  • docker容器互通方式
  • k8s的node亲和性和pod亲和性和反亲和性 污点 cordon drain
  • prometheus常用exporter
  • [含完整代码]Linux使用.sh脚本自动部署(启动|停止|状态|日志)项目[超详细]
  • Unity URP下阴影锯齿
  • 数据结构:堆和堆排序
  • 基于elementUI的el-table组件实现按住某一行数据上下滑动选中/选择或取消选中/选择鼠标经过的行
  • cfa一级考生复习经验分享系列(十八)
  • 影响邮件打开率的因素有哪些?
  • C //练习 5-4 编写函数strend(s, t)。如果字符串t出现在字符串s的尾部,该函数返回1;否则返回0。
  • Java多线程并发篇----第十三篇
  • JsonPath
  • test-04-test case generate 测试用例生成 tcases 快速开始
  • 【进程调度】基于优先级的轮转调度C++实现算法
  • 四川甘孜炉霍县觉日寺管委会主任呷玛降泽被查
  • 娃哈哈:调整产销布局致部分工厂停工,布局新产线可实现自主生产,不排除推新品牌
  • 美国调整对华加征关税
  • 大外交|巴西总统卢拉第六次访华签署20项协议,“双方都视对方为机遇”
  • 上海首发经济“卷”到会展业,浦东签约三个年度“首展”
  • 中美是否计划讨论美方以芬太尼为由对华征收的特别关税?外交部回应