rust宏
宏看起来和函数很像,只不过名称末尾有一个感叹号 ! 。
宏并不产生函数调用,而是展开成源码,并和程序的其余部分一起被编译。
Rust宏和C不同,Rust的宏会展开为抽象语法树(AST,abstract syntax tree),而不是直接字符串替换,这样就不会产生无法预料的优先级错误。
宏有两种:声明宏和过程宏
一、声明宏
(一)定义
声明宏通过macro_rules! 创建
每个声明宏都有一个名称和一条或多条规则。
每条规则都有两部分:一个匹配器(matcher),描述它匹配的句法;一个转换器(transcriber),描述成功匹配后将替代调用的句法。
匹配器和转换器都必须由定界符(delimiter)包围。
宏可以替换为表达式、语句、程序项、类型、模式
1.语法格式
macro_rules! macro_name {// 匹配器和转换器(pattern1) => { /* code1 */ };(pattern2) => { /* code2 */ };...
}
其中macro_name是宏的名字,pattern是匹配器,要匹配的模式,code是转换器,需要替换成的代码。可以看出跟match非常相似。
2.宏参数
在匹配器中使用$ IDENTIFIER : MacroFragSpec捕获宏参数
在转换器中使用$ IDENTIFIER表示宏参数
MacroFragSpec叫做指示符,表示参数的类型
有下列这些指示符
item条目, 例如函数、结构体、模块等
block代码块,
stmt语句(statement)
pat模式(pattern)
expr表达式
ty类型(type) 例如u8 u16
ident标识符 例如结构体、函数的名字
path路径 例如::std::mem::replace
meta元条目 例如 #[…] #![…]
tt标记树 token tree
vis可见性描述符 例如pub
literal 用于字面量
meta 元信息,例如 #[…]和 #![rust macro…] 属性
例子
macro_rules! create_function {// 此宏接受一个 `ident` 类型的参数,并创建一个名为 `$func_name` 的函数。($func_name:ident) => (fn $func_name() {// `stringify!` 宏把 `ident` 转换成字符串。println!("You called {:?}()", stringify!($func_name))})
}
// 借助上述宏来创建名为 `foo` 和 `bar` 的函数。
create_function!(foo);
create_function!(bar);
macro_rules! print_result {// 此宏接受一个 `expr` 类型的表达式,并将它作为字符串,连同其结果一起// 打印出来。// `expr` 指示符表示表达式。($expression:expr) => (// `stringify!` 把表达式*原样*转换成一个字符串。println!("{:?} = {:?}", stringify!($expression), $expression))
}
fn main() {foo();bar();print_result!(1u32 + 1);print_result!({let x = 1u32;x * x + 2 * x - 1});
}
3.宏重载
宏可以重载,从而接受不同的参数组合。也就是说,宏可以有多条规则。
类似于match代码块
例子
// 根据你调用它的方式,`test!` 将以不同的方式来比较 `$left` 和 `$right`。
macro_rules! test {// 参数不需要使用逗号隔开。// 参数可以任意组合!($left:expr; and $right:expr) => (println!("{:?} and {:?} is {:?}",stringify!($left),stringify!($right),$left && $right));($left:expr; or $right:expr) => (println!("{:?} or {:?} is {:?}",stringify!($left),stringify!($right),$left || $right));
}
fn main() {test!(1i32 + 1 == 2i32; and 2i32 * 2 == 4i32);test!(true; or false);
}
4.参数重复
在匹配器中使用 + 来表示可能出现一个或多个参数,使用 * 来表示出现零个或多个参数,用于可变参数列表。
* — 表示任意数量的重复元。
+ — 表示至少有一个重复元。
? — 表示一个可选的匹配段,可以出现零次或一次
例子
// `min!` 将求出任意数量的参数的最小值。
macro_rules! find_min {// 基本情形:($x:expr) => ($x);// `$x` 后面跟着至少一个 `$y,`($x:expr, $($y:expr),+) => (// 对 `$x` 后面的 `$y` 们调用 `find_min!`std::cmp::min($x, find_min!($($y),+)))
}
fn main() {println!("{}", find_min!(1u32));println!("{}", find_min!(1u32 + 2 , 2u32));println!("{}", find_min!(5u32, 2u32 * 3, 4u32));
}
5.作用域
#[macro_export] 表示只要导入了定义这个宏的 crate,该宏就应该是可用的。如果没有该属性,这个宏就不能被引入
例子
#[macro_export]
macro_rules! vec {( $( $x:expr ),* ) => {{let mut temp_vec = Vec::new();$(temp_vec.push($x);)*temp_vec}};
}
(二)使用
宏调用是在编译时执行宏,用执行结果替换该调用。
通过宏名后跟一个!和一个参数列表调用宏
比如
some_extension!(...)
some_extension!{...}
some_extension![...]
例子
// 作为表达式使用
let x = vec![1,2,3];
// 作为语句使用
println!("Hello!");
// 在模式中使用
macro_rules! pat {($i:ident) => (Some($i))
}
if let pat!(x) = Some(1) {assert_eq!(x, 1);
}
// 在类型中使用
macro_rules! Tuple {{ $A:ty, $B:ty } => { ($A, $B) };
}
type N2 = Tuple!(i32, i32);
// 作为程序项使用
thread_local!(static FOO: RefCell<u32> = RefCell::new(1));
// 作为关联程序项使用
macro_rules! const_maker {($t:ty, $v:tt) => { const CONST: $t = $v; };
}
trait T {const_maker!{i32, 7}
}
// 宏内调用宏
macro_rules! example {() => { println!("Macro call in a macro!") };
}
// 外部宏 `example` 展开后, 内部宏 `println` 才会展开.
example!();
二、过程宏
过程宏(procedural macros),更像函数。过程宏接收一些 代码,然后产生另一些代码,而非像声明式宏那样匹配对应模式然后以另一部分代码替换当前代码。
过程宏在编译时运行
过程宏必须在 crate类型为 proc-macro 的crate中定义。这种类型的crate总是链接编译器提供的 proc_macro crate。proc_macro crate提供了编写过程宏所需的各种类型和工具来让编写更容易。此crate主要包含了一个 TokenStream 类型。
注意: 使用Cargo时,定义过程宏的crate的配置文件里要做如下设置:
[lib]
proc-macro = true
过程宏有三种:
(一)类函数过程宏
类函数过程宏是使用宏调用运算符(!)调用的过程宏。
这种宏是由一个带有 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)属性定义新输入。这类宏在给定输入结构体(struct)、枚举(enum)或联合体(union) token流的情况下创建新程序项。它们也可以定义派生宏辅助属性。
派生宏由带有 proc_macro_derive属性和 (TokenStream) -> TokenStream签名的公有可见性函数定义。
输入TokenStream 是带有 derive 属性的程序项的token流。输出TokenStream必须是一组程序项,然后将这组程序项追加到输入TokenStream中的那条程序项所在的模块或块中。
下面是派生宏的一个示例。它没有对输入执行任何有用的操作,只是追加了一个函数 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());
}
派生宏辅助属性
派生宏可以将额外的属性添加到它们所在的程序项的作用域中。这些属性被称为派生宏辅助属性。这些属性是惰性的,它们存在的唯一目的是将这些属性在使用现场获得的属性值反向输入到定义它们的派生宏中。也就是说所有该宏的宏应用都可以看到它们。
定义辅助属性的方法是在 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: ()
}
(三)属性宏
属性宏用于自定义属性。虽然rust有许多内置属性,但有时我们需要自定义属性,这就是宏属性。
属性宏由带有 proc_macro_attribute属性和 (TokenStream, TokenStream) -> TokenStream签名的公有可见性函数定义。
签名中的第一个 TokenStream 是属性名称后面的定界token树(delimited token tree)(不包括外层定界符)。如果该属性作为裸属性(bare attribute)给出,则第一个 TokenStream 值为空。第二个 TokenStream 是程序项的其余部分,包括该程序项的其他属性。输出的 TokenStream 将此属性宏应用的程序项替换为任意数量的程序项。
例如,下面这个属性宏接受输入流并按原样返回,实际上对属性并无操作。
#![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
}
下面示例在编译时输出
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
}
使用属性宏
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() {}"
相关文章:
rust宏
宏看起来和函数很像,只不过名称末尾有一个感叹号 ! 。 宏并不产生函数调用,而是展开成源码,并和程序的其余部分一起被编译。 Rust宏和C不同,Rust的宏会展开为抽象语法树(AST,abstract syntax treeÿ…...

性能测试之性能测试指标详解
前言 刚开始,以为做性能测试,就是做些脚本、参数化、关联,压起来之后,再扔出一个结果。 但实际上不止这些内容,还要加上性能分析,关注调优之后响应时间有多大的提升,TPS 有多大的提高…...

CustomNavBar 自定义导航栏视图
1. 创建偏好设置键 CustomNavBarTitlePreferenceKey.swift import Foundation import SwiftUI//State private var showBackButton: Bool true //State private var title: String "Title" //"" //State private var subtitle: String? "SubTitl…...
canal rocketmq
上篇文章canal 消费进度说到直接使用ClusterCanalConnector并发消费是有问题的,可以先用单点将canal事件发送到mq中,再由mq并发处理,另外mq还可以做到削峰的作用,让canal数据不至于阻塞。 使用队列,可以自己起一个单实…...

【数据库系统概论】第九章关系查询处理何查询优化
9.1查询处理 一:查询处理步骤 关系数据库管理系统查询处理可以分为4个阶段: 查询分析查询检查查询优化查询执行 (1)查询分析 任务:对查询语句进行扫描,分析词法、语法是否符合SQL语法规则 如果没有语…...
bp盐丘模型波场数值模拟matlab
波场数值模拟是地震勘探和地震学研究中常用的工具,而BP(Backpropagation)盐丘模型是一种用于地下介质成像的方法。如果您想在MATLAB中进行波场数值模拟,并结合BP盐丘模型进行地下成像,可以按照以下步骤进行:…...

结构体对齐规则
1.第一个成员在结构体变量偏移量为0的地址处。 2.其他成员变量对齐到某个数字(对齐数)的整数倍的地址处。(对齐数编译器默认的一个对齐数与该成员大小的较小值)注意:目前有且只有VS编译器有默认为8. 3.结构体总大小为最大对齐数的整数倍。 4.如果嵌套…...

css 如何让元素内部文本和外部文本 一块显示省略号
实际上还是有这样的需求的 <div class"container"><span>啊啊啊啊啊啊啊啊</span>你好啊撒撒啊撒撒撒撒啊撒撒撒撒撒说</div>还是有这样的需求的哦。 div.container {width: 200px;white-space: nowrap;text-overflow: ellipsis;overflow:…...

SQL语句-中级
一、Mysql软件使用 1.启动/停止Mysql服务器 任务管理器 cmd命令:以管理员的身份打开cmd命令行 net start mysql80//开启net stop mysql80//停止 2.连接与断开Mysql服务器 注意要在bin目录下执行:-u用户名root,-p密码 mysql -u root -p 可能出现的…...

巧用h2-database.jar连接数据库
文章目录 一 、概述二、实践三、解决办法 一 、概述 H2 Database是一个开源的嵌入式数据库引擎,采用java语言编写,不受平台的限制,同时H2 Database提供了一个十分方便的web控制台用于操作和管理数据库内容。H2 Database还提供兼容模式&#…...
136.只出现一次的数字
136. 只出现一次的数字 - 力扣(LeetCode) 给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题,且…...
mysql中遇到查询字段的别名与函数冲突问题
比如以下哎,我查询城市行业数量排名 select City, DENSE_RANK() over(ORDER BY COUNT(Id) DESC) rank, COUNT(Id) num,IndustrySubGroupName from base_companyinfo WHERE IndustrySubGroupName工业机器人 GROUP BY City 上面使用 DENSE_RANK() 函数来计算排名&am…...
直播获奖
题目描述 NOI2130 即将举行。为了增加观赏性, CCF 决定逐一评出每个选手的成 绩,并直播即时的获奖分数线。本次竞赛的获奖率为 𝑤% ,即当前排名前 𝑤% 的选手的最低成绩就是即时的分数线。 更具体地,…...

选择适合自身业务的HTTP代理有哪些因素决定?
相信对很多爬虫工作者和数据采集的企业来说,如何选购适合自己业务的HTTP代理是一个特别特别困扰的选题,市面上那么多HTTP代理厂商,好像这家有这些缺点,转头又看到另外一家的缺点,要找一家心仪的仿佛大海捞针。今天我们…...
1.3 do...while实现1+...100 for实现1+...100
思路:两个变量,一个变量存储数据之和,一个变量实现自增就行 do...while int i, s;i 1;s 0;do{s 1;i;} while (i < 100);cout << s << endl; for int i, j0;for (i 1; i < 100; i){j 1;}cout << j << …...
react数据管理之setState与Props
react数据管理之setState与Props setState调用原理 setState 是 React 中用于更新组件状态(state)的方法。它的调用原理可以分为以下几个步骤: 状态的改变:当调用 setState 时,React 会将新的状态对象与当前状态对象…...

如何保护我们的网络安全
保护网络安全是至关重要的,尤其是在今天的数字化时代。以下是一些保护网络安全的基本步骤: 1、使用强密码:使用包含字母、数字和特殊字符的复杂密码。不要在多个网站上重复使用相同的密码。定期更改密码。 2、启用双因素认证 (2FA)ÿ…...

springboot 制造装备物联及生产管理ERP系统
springboot 制造装备物联及生产管理ERP系统 liu1113625581...

Google zxing 生成带logo的二维码图片
环境准备 开发环境 JDK 1.8SpringBoot2.2.1Maven 3.2 开发工具 IntelliJ IDEAsmartGitNavicat15 添加maven配置 <dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.4.0</version> </…...
使用Python计算平面多边形间最短距离
要计算平面多边形间的最短距离,首先需要导入Excel表格中的多边形数据,然后使用GJK(Gilbert-Johnson-Keerthi)算法来判断两个多边形是否重叠。如果两个多边形不重叠,可以计算它们之间的最短距离。 以下是一个基本的Pyt…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...

前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...