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() {}"

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

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

相关文章

SAP PP cs62 提示 输入更改号 - BOM 有历史需求

以上是业务操作人员的 账户 但是IT aLL 这边是warning 不是error 遂去查OSS suim 找 C_STUE_NOH权限对象 赋予权限后 解决了

Hadoop高可用集群(HA)一键启动脚本

高可用集群启动时&#xff0c;需要分别在每个节点上都执行zkServer.sh start启动zookeeper&#xff0c;这个过程比较麻烦&#xff0c;并且当我们节点增多时&#xff0c;这个过程无疑不增加了我们的工作量&#xff0c;因此我们可以写一个一键启动所有节点zookeeper的脚本 脚本实…

Excel 数学

SUM(C1:J1) SUM加總/加法公式&#xff1d;SUM(要加總的範圍)(加總快捷鍵 Alt ) SUM(Sheet1:Sheet4!A1) 跨頁加總公式SUM (分頁:分頁!各分頁要加總的儲存格)SUMIF(D1:D16,"大名",F1:F16) …

XXL-Job分布式任务调度框架-单机模式和分片模式执行任务4

一 调度模式分类 1.1 调度模式 1.单个任务&#xff1a;一个任务实例便可完成 a)单机单任务&#xff1a;单机模式下任何路由模式都只有一个实例执行 b)集群单任务&#xff1a;由路由策略(广播模式除外)选择其中一个实例完成 2.集群部署&#xff1a;每个实例都同时执行一部分…

【c++源码】老飞飞源码完整v15源码(包含数据库前端后端源文件)

老飞飞源码完整v15源码&#xff08;包含数据库前端后端源文件&#xff09;整套程序未加密&#xff0c;资源开放。对于研究游戏流程开发非常有帮助&#xff0c;程序仅供参考学习游戏开发流程。以及框架内容。 测试环境搭建 Visual Studio 2013 SQL Server 2008r Windows 10 和 1…

ArduPilot开源飞控之AP_AHRS

ArduPilot开源飞控之AP_AHRS 1. 源由2. 框架设计2.1 启动代码2.2 任务代码 3. 重要例程3.1 init3.2 update 4. 外部AHRS传感模块4.1 init4.2 update 5. 参考资料 1. 源由 AHRS(Attitude Heading Reference System): 飞控最为重要的一个任务就是姿态、位置、方向计算。 本章节…

有趣的 scanf()

限制接收内容 这里的意思是直接收a、b、c作为ch的内容&#xff0c;遇到其它字符放入缓冲区中。 【scanf("%[...]",ch);只接收[]中的内容作为字符串的内容,将其它的内容放入缓冲区中】 这里将aaa后面的h放入缓冲区&#xff0c;但是没有抛弃掉&#xff0c;故而无法接收…

unity 实现拖动ui填空,并判断对错

参考&#xff1a;https://ask.csdn.net/questions/7971448 根据自己的需求修改为如下代码 使用过程中&#xff0c;出现拖动ui位置错误的情况&#xff0c;修改为使用 localPosition 但是吸附到指定位置却需要用的position public class DragAndDrop : MonoBehaviour, IBeginDr…

如何使用C/C++刷新在终端上已经打印的内容

写本文的起源是因为在安装一些工具的时候&#xff0c;发现在终端上并行安装的情况下&#xff0c;显示安装信息是会修改之前已经打印出来的内容&#xff0c;这是怎么做到的呢&#xff1f;抱着对这个问题的好奇我进行了一些探索。 终端是如何运行的 首先是最关键的问题&#xf…

算法通过村第十四关-堆|青铜笔记|堆结构

文章目录 前言堆的概念和特征堆的构成过程插入操作删除操作总结 前言 若执于空&#xff0c;空亦为障。 --彼得马西森《雪豹》 堆结构是一种非常重要的基础数据结构&#xff0c;也是算法的重要内容&#xff0c;很多题目甚至只能通过用堆来进行&#xff0c;所以我们必须明确什么类…

运维 | 如何在 Centos7.x 上安装 telnet 命令行工具

运维 | 如何在 Centos7.x 上安装 telnet 命令行工具 简介 Telnet 是用于通过TCP/IP网络远程登录计算机的协议。一旦与远程计算机建立了连接&#xff0c;它就会成为一个虚拟终端且允许你与远程计算机通信 快速安装 检测是否安装 rpm -qa telnet-server rpm -qa telnet执行安…

centos-apache-简易搭建静态网页服务器-总结

文章目录 1.XShell2.使用命令安装启动服务器3.上传静态文件4.配置文件5.总结 1.XShell 使用命令行工具连接目标CentOS服务器&#xff1a; 2.使用命令安装启动服务器 使用时注意不要复制注释&#xff08;#符号后面&#xff09; yum install httpd #安装 systemctl start htt…

android 与 flutter 之间的通信

文章目录 前言集成 flutter 混合开发android 与 flutter 之间的通信总结 一、前言 因为flutter 具有跨平台的属性&#xff0c;既可以在android上跑&#xff0c;也能在ios 上跑&#xff0c;所以为了节约开发的成本&#xff0c;减少人力&#xff0c;势必就会用到它。然而已有的…

Matlab地理信息绘图—数据诊断

文章目录 数据诊断分析&#xff08;均值方差&#xff09;Matlab代码实现结果展示 数据诊断分析&#xff08;均值方差&#xff09; 均值方差检测是一种简单但有效的异常检测方法&#xff0c;主要基于样本的均值和方差的统计信息。该方法的核心思想是假设正常的样本点应该聚集在…

Anaconda常用命令整理

概要 Anaconda常用命令整理 命令 1. 创建虚拟环境 conda create -n your_env_name pythonx.x # 在创建环境的同时安装必要的包 conda create -n your_env_name numpy matplotlib pythonx.x # 在指定的虚拟环境中安装额外的包 conda install -n your_env_name package_name 2…

CNN-generated images are surprisingly easy to spot... for now

CNN-generated images are surprisingly easy to spot… for now----《目前CNN生成的图像非常容易被发现》 背景&#xff1a; 研究者们发现&#xff0c;仅仅对一种由CNN模型生成的图像进行训练的分类器&#xff0c;也可以检测许多其他模型生成的结果。由此提出这样的观点&#…

中华人民共和国网络安全法

中华人民共和国网络安全法 《中华人民共和国网络安全法》已由中华人民共和国第十二届全国人民代表大会常务委员会第二十四次会议于2016年11月7日通过&#xff0c;现予公布&#xff0c;自2017年6月1日起施行。2022年9月12日&#xff0c;国家互联网信息办公室发布关于公开征求《…

Java面试题-Java核心基础-第二天(基本语法)

目录 一、注释有几种形式 二、标识符与关键字的区别 三、自增自减运算符 四、移位运算符 五、continue、break、return的区别 一、注释有几种形式 注释除了有其他编程语言有的单行注释和多行注释之外&#xff0c;还有其Java特有的文档注释 文档注释能够使用javadoc命令就…

C语言中的自定义类型详解(结构体 + 枚举 + 联合(共用体))

文章目录 1. 结构体1.1 结构体的声明1.2 结构体成员的访问1.3 匿名结构体1.4 结构体的自引用1.5 结构体内存对齐&#xff08;计算结构体的大小&#xff09;1.6 结构体传参1.6.1 传值传递1.6.2 传址传递&#xff08;使用指针&#xff09; 2. 位段2.1 什么是位段&#xff1f;2.2 …

Webpack 什么是loader?什么是plugin?loader与plugin区别是什么?

什么是loader&#xff1f;什么是plugin&#xff1f; loader 本质为一个函数&#xff0c;将文件编译成可执行文件。webpack完成的工作是将依赖分析与tree shinking对于类似.vue或.scss结尾的文件无法编译理解这就需要实现一个loader完成文件转译成js、html、css、json等可执行文…