Rust 的四大类型的宏 (元编程)

文章目录

    • 概念
    • 函数宏或声明宏(Function Macro)
    • 过程宏(Procedural Macro)
      • 类函数的过程宏(Function-like-procedural-macros)
      • 派生宏(Derive Macro)
        • 派生宏附加其他属性
      • 属性宏(Attribute Macro)
    • 示例

概念

Rust 的宏体系:主要分为声明式函数宏 Function Macro过程宏(Procedural Macro),其中过程宏 (proc_macro)又有类函数的过程宏(Function-like-procedural-macros)、过程属性宏(Attribute Macro)和过程派生宏(Derive Macro)。而属性宏(Attribute Macro)和派生宏(Derive Macro)是两个比较常见常用的类型。他们各自具有不同的特性和用途,可以根据需要选择合适的宏类型来实现特定的代码生成或元编程需求。

过程宏允许在编译时运行对 Rust 句法进行操作的代码,它可以在消费掉一些 Rust 句法输入的同时产生新的 Rust 句法输出。可以将过程宏想象成是从一个 AST 到另一个 AST 的函数映射。

过程宏有两种报告错误的方法。首先是 panic;第二个是发布 compile_error 性质的宏调用。

函数宏或声明宏(Function Macro)

函数宏是一种宏,它接受输入并生成输出。它们可以像函数一样接受参数,并使用类似于宏的语法进行处理和转换。函数宏使用 macro_rules! 关键字定义,并使用 ! 符号调用。

例如,在下面的示例中,我们定义了一个简单的函数宏 hello_macro!,它接受一个参数并生成一个打印语句:

   macro_rules! hello_macro {($name:expr) => {println!("Hello, {}!", $name);};}fn main() {hello_macro!("World"); // 输出:Hello, World!}

过程宏(Procedural Macro)

过程宏是一种更强大和灵活的宏,它允许在编译时根据 Rust 代码的结构进行更复杂的代码生成和转换。过程宏是通过创建一个实现特定 trait 的自定义宏来定义的。这些过程宏可以用于生成代码、属性处理、代码转换和其他元编程任务。

另外,Rust 标准库中的 proc-macro 模块提供了用于编写自定义过程宏的相关类型和函数,例如 TokenStreamTokenTree 等。

类函数的过程宏(Function-like-procedural-macros)

类函数过程宏是使用宏调用运算符(!)调用的过程宏。

这种宏是由一个带有 proc_macro属性 和 (TokenStream) -> TokenStream 签名的 公有可见性函数定义。输入 TokenStream 是由宏调用的定界符界定的内容,输出 TokenStream 将替换整个宏调用。

例如,下面的宏定义忽略它的输入,并将函数 answer 输出到它的作用域。

#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;#[proc_macro]
pub fn make_answer(_item: TokenStream) -> TokenStream {"fn answer() -> u32 { 42 }".parse().unwrap()
}

然后我们用它在一个二进制 crate 里打印 “42” 到标准输出。

extern crate proc_macro_examples;
use proc_macro_examples::make_answer;make_answer!();fn main() {println!("{}", answer());
}

派生宏(Derive Macro)

派生宏是一种特殊类型的宏,它用于根据用户自定义的类型自动生成代码。派生宏通常用于为结构体、枚举或 trait 实现自动生成常见的实现代码。

自定义派生宏由带有 proc_macro_derive属性和 (TokenStream) -> TokenStream签名的公有可见性函数定义。

派生宏使用 #[derive(...)] 语法,并放置在类型定义上方。它们可以自动为类型实现特定的 trait 或生成相关的代码。

例如,使用 #[derive(Debug)] 可以自动生成针对调试输出的实现,#[derive(Clone, Copy)] 可以自动生成克隆和复制的实现。

用户也可以通过编写自己的派生宏来自定义生成的代码,例如,实现自定义的序列化、反序列化逻辑或其他定制行为。

需要注意的是,属性宏和派生宏都是 Rust 中的过程宏(Procedural Macro)的一种。它们通过自定义宏来扩展或生成代码,提供了更大的灵活性和元编程能力,可以根据需要修改或生成 Rust 代码的结构和行为。

派生宏附加其他属性

派生宏可以将额外的属性添加到它们所在的程序项的作用域中。这些属性被称为派生宏辅助属性。这些属性是惰性的,它们存在的唯一目的是将这些属性在使用现场获得的属性值反向输入到定义它们的派生宏中。也就是说所有该宏的宏应用都可以看到它们。

关于活跃属性和惰性属性:属性要么是活跃的,要么是惰性的。在属性处理过程中,活跃属性将自己从它们所在的对象上移除,而惰性属性依然保持原位置不变。

cfgcfg_attr 属性是活跃的。test属性在为测试所做的编译形式中是惰性的,在其他编译形式中是活跃的。宏属性是活跃的。所有其他属性都是惰性的。

定义辅助属性的方法是在 proc_macro_derive 宏中放置一个 attributes 键,此键带有一个使用逗号分隔的标识符列表,这些标识符是辅助属性的名称。

例如,下面的派生宏定义了一个辅助属性 helper,但最终没有用它做任何事情。

#![crate_type="proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;#[proc_macro_derive(HelperAttr, attributes(helper))]
pub fn derive_helper_attr(_item: TokenStream) -> TokenStream {TokenStream::new()
}

然后在一个结构体上使用这个派生宏:

#[derive(HelperAttr)]
struct Struct {#[helper] field: ()
}

派生宏示例一:自定义的派生宏

下面是派生宏的一个示例。它没有对输入执行任何有用的操作,只是追加了一个函数 answer

#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;#[proc_macro_derive(AnswerFn)]
pub fn derive_answer_fn(_item: TokenStream) -> TokenStream {"fn answer() -> u32 { 42 }".parse().unwrap()
}

然后使用这个派生宏:

extern crate proc_macro_examples;
use proc_macro_examples::AnswerFn;#[derive(AnswerFn)]
struct Struct;fn main() {assert_eq!(42, answer());
}

派生宏示例二:派生系统内建的宏

// 定义一个派生宏 HelloDebug,为结构体自动生成 Debug trait 的实现
#[derive(Debug)]
struct HelloDebug {name: String,
}fn main() {let hello = HelloDebug {name: "World".to_string(),};println!("{:?}", hello); // 输出:HelloDebug { name: "World" }
}

在上述示例中,我们定义了一个名为 HelloDebug 的结构体,并为其应用了派生宏 #[derive(Debug)]。这将自动生成 Debug trait 的实现,使我们能够使用 println! 宏打印出结构体的调试信息。在 main 函数中,我们创建了一个 HelloDebug 实例,并通过 println! 打印出结构体的调试信息。

派生宏示例三:派生第三方定义的宏

// 定义一个派生宏 Builder,为结构体自动生成 builder 模式的代码
#[derive(Builder)]
struct Person {name: String,age: u32,address: String,
}fn main() {let person = Person::new().name("John").age(30).address("123 Street").build();println!("{:?}", person);
}

在上述示例中,我们使用派生宏 #[derive(Builder)] 为结构体 Person 自动生成 builder 模式的代码。这使我们能够使用链式调用的方式创建 Person 实例,并在 build 方法中构建最终的对象。在 main 函数中,我们使用 builder 模式创建了一个 Person 实例,并打印出其信息。

这是一个更复杂的示例,展示了派生宏可以用于生成更多的代码,例如构建器模式、序列化和反序列化的代码等。

属性宏(Attribute Macro)

属性宏是一种基于属性的宏,用于修改、扩展或注解 Rust 代码。它们通常用于为函数、结构体、枚举、模块等添加元数据或自定义行为。

属性宏使用 #[...] 语法,可以应用于各种语法结构,例如函数、结构体等。它们可以接收属性中的参数,并根据需要对代码进行转换、生成额外的代码或执行其他逻辑。当出现 #![...] 时候,表示该属性应用于当前模版。

属性宏由带有 proc_macro_attribute属性和 (TokenStream, TokenStream) -> TokenStream签名的公有可见性函数定义。签名中的第一个 TokenStream 是属性名称后面的定界 token树。如果该属性作为裸属性(bare attribute)给出,则第一个 TokenStream 值为空。第二个 TokenStream 是程序项的其余部分,包括该程序项的其他属性。

示例

当谈到属性宏和派生宏时,以下是在 Rust 中的代码示例:

属性宏示例一:

例如,下面这个属性宏接受输入流并按原样返回,实际上对属性并无操作。

#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;#[proc_macro_attribute]
pub fn return_as_is(_attr: TokenStream, item: TokenStream) -> TokenStream {item
}

下面示例显示了属性宏看到的字符串化的 TokenStream。输出将显示在编译时的编译器输出窗口中。(具体格式是以 "out:"为前缀的)输出内容也都在后面每个示例函数后面的注释中给出了。

// my-macro/src/lib.rs
extern crate proc_macro;
use proc_macro::TokenStream;#[proc_macro_attribute]
pub fn show_streams(attr: TokenStream, item: TokenStream) -> TokenStream {println!("attr: \"{}\"", attr.to_string());println!("item: \"{}\"", item.to_string());item
}

下面示例显示了属性宏看到的字符串化的 TokenStream。输出将显示在编译时的编译器输出窗口中。(具体格式是以 "out:"为前缀的)输出内容也都在后面每个示例函数后面的注释中给出了。

// src/lib.rs
extern crate my_macro;use my_macro::show_streams;// 示例: 基础函数
#[show_streams]
fn invoke1() {}
// out: attr: ""
// out: item: "fn invoke1() { }"// 示例: 带输入参数的属性
#[show_streams(bar)]
fn invoke2() {}
// out: attr: "bar"
// out: item: "fn invoke2() {}"// 示例: 输入参数中有多个 token 的
#[show_streams(multiple => tokens)]
fn invoke3() {}
// out: attr: "multiple => tokens"
// out: item: "fn invoke3() {}"// 示例:
#[show_streams { delimiters }]
fn invoke4() {}
// out: attr: "delimiters"
// out: item: "fn invoke4() {}"

属性宏示例二:

// 定义一个属性宏 hello_attribute,将传入的字符串包装在 println! 宏中
#[proc_macro_attribute]
pub fn hello_attribute(attr: TokenStream, input1: TokenStream) -> TokenStream {let output = format!("println!(\"Hello, {}\");", attr.to_string());println!("{}", output);input1
}// 使用 hello_attribute 宏为函数添加注解
#[hello_attribute("World")]
fn greet() {// 生成的代码将打印 "Hello, World"
}

在上述示例中,我们定义了一个名为 hello_attribute 的属性宏,它将传入的字符串包装在 println! 宏中。然后,我们使用 hello_attribute 宏对 greet 函数进行注解,在运行时将打印 “Hello, World”。

当涉及到更复杂的示例时,属性宏和派生宏可以实现更多的功能和代码转换。以下是更复杂一点的示例:

属性宏示例三:

// 定义一个属性宏 repeat,将函数体重复执行指定次数
#[proc_macro_attribute]
pub fn repeat(attr: TokenStream, input: TokenStream) -> TokenStream {let repeat_count = attr.to_string().parse::<u32>().unwrap();let input_fn = input.to_string();let mut output = TokenStream::new();if let Some(index) = input_fn.find('(') {for i in 0..repeat_count {let ret = format!("{}{}{}", &input_fn[..index], i+1, &input_fn[index..]);println!("fn: \"{}\"", ret);output.extend(ret.parse::<TokenStream>().unwrap());}}output
}// 使用 repeat 宏为函数添加注解,使函数体重复执行 3 次
#[repeat(3)]
fn greet() {println!("Hello, world!");
}

在上述示例中,我们定义了一个名为 repeat 的属性宏。该宏接受一个参数 attr,表示重复执行的次数,并将函数体 input 重复执行指定次数。在 greet 函数上方使用 #[repeat(3)] 注解,将函数体定义了 3 个类似的函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/61188.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

@Async引发的循环依赖问题

这个以前分析过了&#xff0c;但是再看的时候感觉写的太乱了(流水账)&#xff0c;这个是精简版本。 bug复现 先上bug&#xff0c;众所周知&#xff0c;spring通过实例化和属性注入分开解决了循环依赖&#xff0c;理论上也不会有问题。但是意外就那么来了&#xff0c;经过排查是…

CI/CD 持续集成 持续交付

CI&#xff08;Continuous integration&#xff09;持续集成 参考&#xff1a;https://www.jianshu.com/p/2132949ff84a 持续集成是指多名开发者在开发不同功能代码的过程当中&#xff0c;可以频繁的将代码行合并到一起并切相互不影响工作。 持续集成的目的&#xff0c;是让…

操作符算数转换题

目录 1.交换两个变量&#xff08;不创建临时变量&#xff09; 2.统计二进制中1的个数 3.打印整数二进制的奇数位和偶数位 4.求两个数二进制中不同位的个数 5.【一维数组】有序序列合并 6.获得月份天数 7.变种水仙花数 8.选择题总结tips 这篇博文主要分享操作符&算…

ChatGPT:引领人机交互的未来

前言 在信息技术飞速发展的时代&#xff0c;人机交互的方式也在不断演进。技术对人们生活和工作的影响。本文将带您深入探讨一款引领人机交互未来的人工智能模型——ChatGPT。 ChatGPT简介 ChatGPT 是一种由开放AI&#xff08;OpenAI&#xff09;开发的人工智能模型&#xf…

WIndows 配置多版本python环境,非常清晰明了

配置多个python环境 下面以配置三个python版本环境为例子 首先下载好三个环境的python&#xff0c;如&#xff1a;python2.7、python3.6、python3.10 给个官网链接自己下&#xff0c;想要几版本就下几&#xff1a;https://www.python.org/downloads/windows/ 下载完成后将pyth…

C语言控制语句——跳转关键字

循环和switch专属的跳转&#xff1a;break循环专属的跳转&#xff1a;continue无条件跳转&#xff1a;goto break 循环的break说明 某一条件满足时&#xff0c;不再执行循环体中后续重复的代码&#xff0c;并退出循环 需求&#xff1a;一共吃5碗饭, 吃到第3碗吃饱了, 结束吃饭…

Apache SeaTunnel 2.3.3 版本发布,CDC 支持 Schema Evolution!

时隔两个月&#xff0c; Apache SeaTunnel 终于迎来大版本更新。此次发布的 2.3.3 版本在功能和性能上均有较大优化改进&#xff0c;其中大家期待已久的 CDC Schema evolution&#xff08;DDL 变更同步&#xff09;、主键 Split 拆分、JDBC Sink 自动建表功能、SeaTunnel Zeta …

Java正则表达式系列--Pattern和Matcher的使用

原文网址&#xff1a;Java正则表达式系列--Pattern和Matcher的使用_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍Java的正则表达式中的两个重要类的用法&#xff1a;Pattern和Matcher。 在Java中&#xff0c;java.util.regex包定义了正则表达式使用到的相关类&#xff0c…

如何以Base64形式存储、返回图片数据

在Java中&#xff0c;可以使用Base64类来将图片转换为Base64编码。下面是一个示例代码&#xff1a; Java代码直接处理&#xff1a; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Base64; public class ImageToBase64…

【GO】LGTM_Grafana_Tempo(2)_官方用例改后实操

最近在尝试用 LGTM 来实现 Go 微服务的可观测性&#xff0c;就顺便整理一下文档。 Tempo 会分为 4 篇文章&#xff1a; Tempo 的架构官网测试实操跑通gin 框架发送 trace 数据到 tempogo-zero 微服务框架使用发送数据到 tempo 根据官方文档实操跑起来 tempo&#xff0c;中间根…

为何反射探针关闭Mipmap后变成了白图

1&#xff09;为何反射探针关闭Mipmap后变成了白图 2&#xff09;2021.3 Android从AssetBundle中加载视频播放失败问题 3&#xff09;SBP是否可以解决打包时FBX等模型文件中额外的GameObject 4&#xff09;Addressables加载已打包过的Prefab后Mono脚本丢失 这是第349篇UWA技术知…

servlet初体验之环境搭建!!!

我们需要用到tomcat服务器&#xff0c;咩有下载的小伙伴看过来&#xff1a;如何正确下载tomcat&#xff1f;&#xff1f;&#xff1f;_明天更新的博客-CSDN博客 1. 创建普通的Java项目&#xff0c;并在项目中创建libs目录存放第三方的jar包。 建立普通项目 创建libs目录存放第三…

高速公路自动驾驶汽车超车控制方法研究

目录 摘要 ............................................................................................................ I Abstract ...................................................................................................... II 目录 ...............…

ZooKeeper集群环境搭建

&#x1f947;&#x1f947;【大数据学习记录篇】-持续更新中~&#x1f947;&#x1f947; 个人主页&#xff1a;beixi 本文章收录于专栏&#xff08;点击传送&#xff09;&#xff1a;【大数据学习】 &#x1f493;&#x1f493;持续更新中&#xff0c;感谢各位前辈朋友们支持…

端到端自动驾驶综述

End-to-end Autonomous Driving: Challenges and Frontiers 文章脉路 Introduction 从经典的模块化的方法到端到端方法的一个对比, 讲了各自的优缺点, 模块化的好处是各个模块都有自己明确的优化的目标, 可解释性较强, 且容易debug, 缺点是各个模块优化的目标并不是最终的驾…

开始MySQL之路——MySQL的DataGrip图形化界面

下载DataGrip 下载地址&#xff1a;Download DataGrip: Cross-Platform IDE for Databases & SQL 安装DataGrip 准备好一个文件夹&#xff0c;不要中文和空格 C:\Develop\DataGrip 激活DataGrip 激活码&#xff1a; VPQ9LWBJ0Z-eyJsaWNlbnNlSWQiOiJWUFE5TFdCSjBaIiwibGl…

DockerCompose介绍与使用

DockerCompose介绍与使用 1、DockerCompose介绍 DockerCompose用于定义和运行多容器 Docker 应用程序的工具。 通过 Compose可以使用 YAML 文件来配置应用程序需要的所有服务。一个使用Docker容器的应用&#xff0c;通常由多个容器组成&#xff0c;使用Docker Compose不再需要…

vue naive ui 按钮绑定按键

使用vue (naive ui) 绑定Enter 按键 知识点: 按键绑定Button全局挂载使得message,notification, dialog, loadingBar 等NaiveUI 生效UMD方式使用vue 与 naive ui将vue默认的 分隔符大括号 替换 为 [[ ]] <!DOCTYPE html> <html lang"en"> <head>…

LeetCode-452-用最少数量的箭引爆气球

题目描述&#xff1a; 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points &#xff0c;其中points[i] [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。 一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出…

Myabtis学习记录

mapper.xml常用标签 foreach标签 MyBatis中mapper.xml中foreach的使用_mapper.xml foreach_Willing卡卡的博客-CSDN博客 if标签 Mybatis的mapper.xml中if标签test判断的用法_if标签的test_大唐冠军侯的博客-CSDN博客