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()函数创建,也可以通过其他…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...
