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 模块提供了用于编写自定义过程宏的相关类型和函数,例如 TokenStream 和 TokenTree 等。
类函数的过程宏(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 代码的结构和行为。
派生宏附加其他属性
派生宏可以将额外的属性添加到它们所在的程序项的作用域中。这些属性被称为派生宏辅助属性。这些属性是惰性的,它们存在的唯一目的是将这些属性在使用现场获得的属性值反向输入到定义它们的派生宏中。也就是说所有该宏的宏应用都可以看到它们。
关于活跃属性和惰性属性:属性要么是活跃的,要么是惰性的。在属性处理过程中,活跃属性将自己从它们所在的对象上移除,而惰性属性依然保持原位置不变。
cfg 和 cfg_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 个类似的函数。
相关文章:
Rust 的四大类型的宏 (元编程)
文章目录 概念函数宏或声明宏(Function Macro)过程宏(Procedural Macro)类函数的过程宏(Function-like-procedural-macros)派生宏(Derive Macro)派生宏附加其他属性 属性宏ÿ…...
探索数据湖中的巨兽:Apache Hive分布式SQL计算平台浅度剖析!
文章目录 ◆ Apache Hive 概述1.1 分布式SQL计算1.2 Hive的优势 ◆ 模拟实现Hive功能2.1 元数据管理2.2 解析器2.3 基础架构2.4 Hive架构 ◆ Hive基础架构3.1 Hive架构图3.2 Hive组件3.2.1 元数据存储3.2.2 Driver驱动程序3.2.3 用户接口 ◆ Hive部署4.1 VMware虚拟机部署步骤一…...
Node.js 的 Buffer 是什么?一站式了解指南
在 Node.js 中,Buffer 是一种用于处理二进制数据的机制。它允许你在不经过 JavaScript 垃圾回收机制的情况下直接操作原始内存,从而更高效地处理数据,特别是在处理网络流、文件系统操作和其他与 I/O 相关的任务时。Buffer 是一个全局对象&…...
延时盲注技术:SQL 注入漏洞检测入门指南
部分数据来源:ChatGPT 引言 在网络安全领域中,SQL 注入漏洞一直是常见的安全隐患之一。它可以利用应用程序对用户输入的不恰当处理,导致攻击者能够执行恶意的 SQL 查询语句,进而获取、修改或删除数据库中的数据。为了帮助初学者更好地理解和检测 SQL 注入漏洞,本文将介绍…...
【Midjourney电商与平面设计实战】创作效率提升300%
不得不说,最近智能AI的话题火爆圈内外啦。这不,战火已经从IT行业燃烧到设计行业里了。 刚研究完ChatGPT,现在又出来一个AI作图Midjourney。 其视觉效果令不少网友感叹:“AI已经不逊于人类画师了!” 现如今,在AIGC 热…...
URI、URL、URIBuilder、UriBuilder、UriComponentsBuilder说明及基本使用
之前想过直接获取url通过拼接字符串的方式实现,但是这种只是暂时的,后续地址如果有变化或参数很多,去岂不是要拼接很长,由于这些等等原因,所以找了一些方法实现 java.net.URI URI全称是Uniform Resource Identifier,也就是统一资源标识符,它是一种采用特定的语法标识一…...
抓包 - 简要总结 - Windows和Android抓包
抓包 - 简要总结 - Windows和Android抓包 前言 小巧且强大的抓包工具“Fiddler”安装可参考我的另一篇博客:抓包 - 经典抓包工具Fiddler的安装与初使用 本文主要介绍如何使用Fiddler抓包Windows和安卓。 Windows 抓包Windows很简单,安装证书&#x…...
iOS脱壳技术(二):深入探讨dumpdecrypted工具的高级使用方法
前言 应用程序脱壳是指从iOS应用程序中提取其未加密的二进制可执行文件,通常是Mach-O格式。这可以帮助我们深入研究应用程序的底层代码、算法、逻辑以及数据结构。这在逆向工程、性能优化、安全性分析等方面都有着重要的应用。 在上一篇内容中我们已经介绍了Clutc…...
4.RabbitMQ高级特性 幂等 可靠消息 等等
一、如何保证生产者生产消息100%的投递成功 保障消息的成功发出保障MQ节点的成功接收发送端收到MQ节点(Broker)确认应答完善的消息进行补偿机制 1. 理解Confirm确认消息机制 消息的确认,是指生产者投递消息后,如果Broker收到消…...
ES常见错误总结
目录 报错信息 复盘 org.elasticsearch.index.query.QueryShardException:No mapping found for [xx] in order to sort on 报错信息 测试环境 org.elasticsearch.index.query.QueryShardException: No mapping found for [xx] in order to sort on 数据不存在的时候或者…...
35、下载、安装 jdk11 记录,Idea中把项目从 jdk8 换 jdk 11
之前一直用jdk8,现在改成 11的试试看 登录官网下载这个11 https://www.oracle.com/cn/java/technologies/downloads/#java11-windows 下载jdk的oracle官网 需要自己注册oracle账户 修改环境变量的 JAVA_HOME Path 路径这里原本添加8的时候有了,不…...
TinyVue - 华为云 OpenTiny 出品的企业级前端 UI 组件库,免费开源,同时支持 Vue2 / Vue3,自带 TinyPro 中后台管理系统
华为最新发布的前端 UI 组件库,支持 PC 和移动端,自带了 admin 后台系统,完成度很高,web 项目开发又多一个选择。 关于 OpenTiny 和 TinyVue 在上个月结束的华为开发者大会2023上,官方正式进行发布了 OpenTiny&#…...
ubuntu下自启动设置,为了开机自启动launch文件
1、书写sh脚本文件 每隔5秒钟启动一个launch文件,也可以直接在一个launch文件中启动多个,这里为了确保启动顺利,添加了一些延时 #! /bin/bash ### BEGIN INIT sleep 5 gnome-terminal -- bash -c "source /opt/ros/melodic/setup.bash…...
脚本:PDF文件批量转换成图片(python3)
文章目录 语言用法源码1源码2 语言 语言:python 3 用法 用法:选择PDF文件所在的目录,点击 确定 后,自动将该目录下的所有PDF转换成单个图片,图片名称为: pdf文件名.page_序号.jpg 如运行中报错,需要自行…...
Spring和mybatis整合
一、Spring整合MyBatis 1. 导入pom依赖 1.1 添加spring相关依赖(5.0.2.RELEASE) spring-core spring-beans spring-context spring-orm spring-tx spring-aspects spring-web 1.2 添加mybatis相关依赖 mybatis核心:mybatis(3.4.5) Mybatis分页:pagehel…...
应知道的python基础知识
1、运算符 2、特殊情况下的逻辑运算 3、循环中的else 3.1 while else 3.2 for else 4、列表相关操作 列表的相关操作 4.1增(append, extend, insert) 通过append可以向列表添加元素:列表.append(新元素数据)通过extend可以将另一个列表中的元素逐一添加到列表中:列表.exte…...
FFmpeg<第一篇>:环境配置
1、官网地址 http://ffmpeg.org/download.html2、linux下载ffmpeg 下载: wget https://ffmpeg.org/releases/ffmpeg-snapshot.tar.bz2解压: tar xvf ffmpeg-snapshot.tar.bz23、FFmpeg ./configure编译参数汇总 解压 ffmpeg-snapshot.tar.bz2 之后&…...
深度学习:Sigmoid函数与Sigmoid层区别
深度学习:Sigmoid函数与Sigmoid层 1. Sigmoid神经网络层 vs. Sigmoid激活函数 在深度学习和神经网络中,“Sigmoid” 是一个常见的术语,通常用来表示两个相关但不同的概念:Sigmoid激活函数和Sigmoid神经网络层。这两者在神经网络…...
❤ Ant Design Vue 2.28的使用
❤ Ant Design Vue 2.28 弹窗 //按钮 <a-button type"primary" click"showModal">Open Modal</a-button>//窗口 <a-modal v-model:visible"visible" title"Basic Modal" ok"handleOk"><p>Some con…...
R语言02-R语言中的向量
概念 在R语言中,向量(Vector)是最基本的数据结构之一,用于存储相同类型的多个元素。向量可以包含数值、字符、逻辑值等,但其中的所有元素必须具有相同的数据类型。向量可以通过c()函数创建,也可以通过其他…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...
2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...
MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...
