当前位置: 首页 > news >正文

2311rust过程宏的示例

原文

Rust2018中的过程宏

Rust2018版本中,我最喜欢的功能是过程宏.在Rust中,过程宏有着悠久而传奇的历史(并继续拥有传奇的未来!)
因为2018年版极大改善了定义和使用它们的体验.

什么是过程宏

过程宏是,在编译时一段语法,生成新语法的函数.Rust2018中的过程宏有三个风格:

1,自Rust1.15以来,#[derive]模式宏一直很稳定,并把#[derive(Debug)]所有优点和易用性也带到了用户定义的特征中,如Serde#[derive(Deserialize)].
2,函数式宏,在2018版中是新的稳定版本,并允许在基于crates.io的库中定义:

env!("FOO") 
format_args!("...")

宏.类似macro_rules!宏.

3,我最喜欢的属性宏,也是2018版中的新功能,它允许在Rust函数上提供轻量注解,来编译时语法转换代码.

可在清单中用proc-macro=true指定宏.使用时,Rust编译器会加载过程宏,并在展开调用时执行它.
Cargo可控制过程宏版本,且可像其他Cargo依赖项一样轻松使用它们!

定义过程宏

这三类的定义方式略微不同,在此以属性宏为例.首先,标记Cargo.toml:

[lib]
proc-macro = true

然后在src/lib.rs中,可编写宏:

extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_attribute]
pub fn hello(attr: TokenStream, item: TokenStream) -> TokenStream {//...
}

然后可在tests/smoke.rs中编写单元测试:

#[my_crate::hello]
fn wrapped_function() {}
#[test]
fn works() {wrapped_function();
}

…就这样!执行cargo test的测试时,Cargo编译过程宏.之后,它编译编译时加载宏的单元测试,执行hello函数并编译生成的语法.

可见过程宏的几个重要属性:

1,输入/输出TokenStream类型
2,编译时可执行任意代码,即几乎不受限!
3,过程宏模块系统整合,即可像其他名字一样导入.

先深入了解其中的一些要点.

宏和模块系统

宏现在与Rust中的模块系统整合.表明导入宏时不再需要笨拙的#[macro_use]属性!不是:

#[macro_use]
extern crate log;
fn main() {debug!("hello, ");info!("world!");
}

你可以如下:

use log::info;
fn main() {log::debug!("hello, ");info!("world!");
}

好处不仅限于!风格的macro_rules宏,因为现在可转换如下代码:

#[macro_use]
extern crate serde_derive;
#[derive(Deserialize)]
struct Foo {//...
}
//为
use serde::Deserialize;
#[derive(Deserialize)]
struct Foo {//...
}

甚至不需要显式依赖Cargo.toml中的serde_derive!,只需要:

[dependencies]
serde = { version = '1.0.82', features = ['derive'] }

TokenStream内部

神秘的TokenStream类型,来自编译器提供的proc_macro仓库.
首次添加TokenStream时,只能调用to_string()parse()来回来转换其为从串转换.
Rust2018开始,可直接操作TokenStream中的令牌.

TokenStream"只是"TokenTree上的一个迭代器.Rust中的所有语法都分四类,即TokenTree的四种变体:
1,Ident是如foobar的标识.它还包含如selfsuper的关键字.
2,字面(Literal)包括像1,"foo""b"等内容.所有字面都是表示程序中常量值一个令牌.
3,Punct表示标点符号,而不是分隔符.

.foo.bar字段访问中的Punct令牌.像=>多符标点符号表示为两个Punct标记,一个表示=,一个表示>,Spacing枚举表示=与>相邻.

4,Group"树"项最相关的地方,因为Group代表一个分隔子令牌流.如,(a,b)是以括号作为分隔符Group,内部令牌流a,b.

最小化TokenTree对稳定性至关重要.
稳定RustAST是不可行的,因为那表示不能改变它.(想像假如如果不能添加?符号).

TokenStream过程宏通信,在同时可编译和处理较旧过程宏时,编译器添加新的语言语法.不过,先看看如何从TokenStream中取有用的信息.

解析TokenStream

但,只需要看看syn仓库.
使用syn仓库,可用单行代码解析RustAST:

#[proc_macro_attribute]
pub fn hello(attr: TokenStream, item: TokenStream) -> TokenStream {let input = syn::parse_macro_input!(item as syn::ItemFn);let name = &input.ident;let abi = &input.abi;//...
}

syn仓库不仅可解析内置语法,且还可轻松地为自己的语法编写递归下降解析器.更多.

生成TokenStream

不仅要以TokenStream作为过程宏的输入,还要生成TokenStream作为输出.一般要求输出是有效的Rust语法,但与输入一样,它只是要构建的令牌列表.
创建TokenStream的唯一方法是通过其FromIterator实现,即必须逐个创建每个令牌并聚集它到TokenStream中.
不过,这很乏味,所以看看synquote兄弟仓库.
quote仓库是Rust的准引用实现,主要提供了一个方便的宏:

use quote::quote;
#[proc_macro_attribute]
pub fn hello(attr: TokenStream, item: TokenStream) -> TokenStream {let input = syn::parse_macro_input!(item as syn::ItemFn);let name = &input.ident;//输入函数总是等价于返回`42`,对不?let result = quote! {fn #name() -> u32 { 42 }};result.into()
}

quote!宏这里允许你编写大部分Rust语法,并用#foo环境快速插值变量.

令牌和跨度(Span)

也许Rust2018中过程宏的最大特性是可自定义和使用每个令牌上的Span信息,这样可从过程宏中取得惊人语法错误消息:

error: expected `fn`--> src/main.rs:3:14|
3 | my_annotate!(not_fn foo() {});|              ^^^^^^

完全自定义的错误消息:

 错误:`导入`方法必须至少`有一个`参数--> invalid-imports.rs:12:5|
12 |     fn f1();|     ^^^^^^^^

Span可看作是原始源文件的指针,一般表示,foo,"Ident令牌来自文件bar.rs,第4行第5列,长度为3个字节".
此信息主要由包含警告和错误消息的编译器诊断使用.

Rust2018中,每个TokenTree都有个与之关联的Span.即,如果把所有输入令牌Span保留到输出中,则即使生成全新语法,编译器的错误消息仍是准确的!
如,如下一个小宏:

#[proc_macro]
pub fn make_pub(item: TokenStream) -> TokenStream {let result = quote! {pub #item};result.into()
}

按如下调用:

my_macro::make_pub! {static X: u32 = "foo";
}

是无效的,因为从应该返回u32的函数返回一个,编译器帮助诊断问题为:

 `error[E0308]`:`类型`不匹配--> src/main.rs:1:37|
1 | my_macro::make_pub!(static X: u32 = "foo");|                                     ^^^^^ expected u32, found reference|=注意:期望类型为`"U32"`找到类型`'&'staticstr'`错误:因为上一个错误而中止

在此可见,尽管正在生成全新语法,但编译器可保留span信息,以继续为编写代码提供针对性的诊断.

生态中的过程宏

syn,quoteproc-macro2是编写过程宏首选库.方便自定义解析器,解析现有语法,创建新语法,使用旧版本Rust等等!

Serde这里及SerializeDeserialize继承宏可能是生态中最常用的宏.它们有令人印象深刻的配置量,是小注解但强大的很好示例.

wasm-bindgen项目在Rust中,使用属性宏轻松定义接口,并从JS导入接口.
#[wasm_bindgen]轻量注解方便理解传入和传出内容,并删除了大量转换样板.

gobject_gen!宏是GNOME项目的实验性IDL,来在Rust中安全地定义GObject对象,避免手写来与C语言通信,并用Rust写其他GObject实例交互期望的所有胶水.

Rocket框架最近切换到了过程宏,并展示了过程宏的一些最新功能,如自定义诊断,自定义跨度创建等.

相关文章:

2311rust过程宏的示例

原文 Rust2018中的过程宏 在Rust2018版本中,我最喜欢的功能是过程宏.在Rust中,过程宏有着悠久而传奇的历史(并继续拥有传奇的未来!) 因为2018年版极大改善了定义和使用它们的体验. 什么是过程宏 过程宏是,在编译时用一段语法,生成新语法的函数.Rust2018中的过程宏有三个风格…...

数据分析:数据预处理流程及方法

数据预处理是数据分析过程中至关重要的一步,它涉及到清洗、转换和整理原始数据,以便更好地适应分析模型或算法。以下是一些常见的数据预处理方法和规则: 数据清洗: 处理缺失值:检测并处理数据中的缺失值,可…...

uniapp 防抖节流封装和使用

防抖(debounce):定义一个时间,延迟n秒执行,n秒内再次调用,会重新计时,计时结束后才会再次执行 主要运用场景: 输入框实时搜索:在用户输入内容的过程中,使用防抖可以减少频繁的查询…...

springcloud alibaba学习视频

阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台...

【MySQL】一些内置函数(时间函数、字符串函数、数学函数等,学会了有妙用)

内置函数 前言正式开始时间函数显示当前日期、时间、日期时间的日期计算相差多少天示例创建一张表,记录生日 留言表 字符串函数charsetconcatinstr(string, substring)ucase和lcaseleft(string, length)length求字符串长度replace(str, search_str, replace_str)tri…...

QtC++与QColumnView详解

介绍 在 Qt 中,QColumnView 是用于显示多列数据的控件,它提供了一种多列列表视图的方式,类似于文件资源管理器中的详细视图。QColumnView 是基于模型/视图架构的,通常与 QFileSystemModel 或自定义模型一起使用。 以下是关于 QC…...

微信小程序配置企业微信的在线客服

配置企业微信后台 代码实现 <button tap"openCustomerServiceChat">打开企业微信客服</button>methods: {openCustomerServiceChat(){wx.openCustomerServiceChat({extInfo: {url: 你刚才的客服地址},corpId: 企业微信的id,showMessageCard: true,});} …...

深入理解Java AQS:从原理到源码分析

目录 AQS的设计原理1、队列节点 Node 和 FIFO队列结构2、state 的作用3、公平锁与非公平锁 AQS 源码解析1、Node节点2、acquire(int)3、release(int)4、自旋&#xff08;Spin&#xff09;5、公平性与 FIFO 基于AQS实现的几种同步器1、ReentrantLock&#xff1a;可重入独占锁2、…...

【数据结构(四)】栈(1)

文章目录 1. 关于栈的一个实际应用2. 栈的介绍3. 栈的应用场景4. 栈的简单应用4.1. 思路分析4.2. 代码实现 5. 栈的进阶应用(实现综合计算器)5.1. 栈实现一位数计算(中缀表达式)5.1.1. 思路分析5.1.2. 代码实现 5.2. 栈实现多位数计算(中缀表达式)5.2.1. 解决思路5.2.2. 代码实…...

实验(四):指令部件实验

一、实验内容与目的 实验要求&#xff1a; 利用CP226实验仪上的小键盘将程序输入主存储器EM&#xff0c;通过指令的执行实现微程序控制器的程序控制。 实验目的&#xff1a; 1.掌握模型机的操作码测试过程&#xff1b; 2.掌握模型机微程序控制器的基本结构以及程序控制的基本原…...

【Android11】在内置的Tvsettings的界面中显示以太网Mac地址

【Android11】在内置的Tvsettings的界面中显示以太网Mac地址 了解Preference必要信息步骤&#xff1a;1. 在设置页面的xml文件中增加一个Preference &#xff0c;这是要显示出来的设置项2. 在strings.xml文件中增加我们在第一步新设置的值3. 为新加的设置项增加一个新的XXXPref…...

在Oracle 11g 数据库上设置透明数据加密(TDE)

本文回答2个问题&#xff1a; 11g下简明的TDE设置过程由于11g不支持在线TDE&#xff0c;介绍2中11g下的加密表空间的迁移方法 设置表空间TDE之前 表空间没有加密时&#xff0c;很容易探测到明文数据&#xff1a; create tablespace unsectbs datafile unsectbs.dbf size 10…...

互动直播 之 视频帧原始数据管理

目录 一、视频帧管理 1、存储图片数据的数据结构 1.1)、图片数据首地址...

基于tcp协议及数据库sqlite3的云词典项目

这个小项目是一个网络编程学习过程中一个阶段性检测项目&#xff0c;过程中可以有效检测我们对于服务器客户端搭建的能力&#xff0c;以及一些bug查找能力。项目的一个简单讲解我发在了b站上&#xff0c;没啥心得&#xff0c;多练就好。 https://t.bilibili.com/86524470252640…...

C/C++内存管理(1):C/C++内存分布,C++内存管理方式

一、C/C内存分布 1.1 1.2 二、C内存管理方式 C可以通过操作符new和delete进行动态内存管理。 2.1 new和delete操作内置类型 int main() {int* p1 new int;// 注意区分p2和p3int* p2 new int(10);// 对*p2进行初始化 10int* p3 new int[10];// p3 指向一块40个字节的int类…...

11 redis中分布式锁的实现

单机锁代码 import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.con…...

循环链表3

插入函数——插入数据&#xff0c;在链表plsit的pos位置插入val数据元素 位置pos&#xff08;在无特别说明的情况下&#xff09;是从0开始计数的 要改变链表结构&#xff0c;就要依赖前驱&#xff0c;每个前驱的next存储着下一个数据结点的地址&#xff0c;也就是依靠前驱的ne…...

如何修改百科内容?百度百科内容怎么修改?

百科词条创建上去是相当不易的&#xff0c;同时修改也是如此&#xff0c;一般情况下&#xff0c;百科词条是不需要修改的&#xff0c;但是很多时候企业或是人物在近期收获了更多成就或是有更多的变动&#xff0c;这个时候就需要补充维护词条了&#xff0c;如何修改百科内容&…...

mysql8.0英文OCP考试第131-140题

Q131.You have upgraded the MySQL binaries from 5.7.28 to 8.0.18 by using an in-place upgrade. Examine the message sequence generated during the first start of MySQL 8.0.18: 。。。[System]。。。/usx/sbin/mysqld (mysqld 8.0.18-commercial) starting as proces…...

MySQL数据库——存储过程-条件处理程序(通过SQLSTATE指定具体的状态码,通过SQLSTATE的代码简写方式 NOT FOUND)

目录 介绍 案例 通过SQLSTATE指定具体的状态码 通过SQLSTATE的代码简写方式 NOT FOUND 介绍 条件处理程序&#xff08;Handler&#xff09;可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。具体语法为&#xff1a; DECLARE handler_action HANDLER FOR c…...

Chromium系浏览器Linux硬件解码全攻略:从VA-API原理到Chrome 91+的flag变迁史

Chromium系浏览器Linux硬件解码技术演进与实战指南 在Linux桌面生态中&#xff0c;视频播放的硬件加速一直是个充满挑战的领域。特别是对于Chromium系浏览器用户而言&#xff0c;从2019年VA-API补丁首次出现在社区构建版本&#xff0c;到2021年Google官方逐步支持&#xff0c;这…...

告别软核!用Zynq UltraScale+ MPSoC EV系列硬核VCU搞定4K60 H.265编解码

硬核加速&#xff1a;Zynq UltraScale MPSoC EV系列VCU在4K60视频处理中的实战解析 当4K60fps视频处理成为工业视觉、自动驾驶和广电传媒的标配需求时&#xff0c;工程师们往往陷入两难&#xff1a;通用处理器难以应对实时编解码的计算洪流&#xff0c;而传统FPGA软核方案又面…...

如何快速上手Asio:10个简单示例带你掌握C++网络编程

如何快速上手Asio&#xff1a;10个简单示例带你掌握C网络编程 【免费下载链接】asio Asio C Library 项目地址: https://gitcode.com/gh_mirrors/as/asio Asio是一个功能强大的C库&#xff0c;专为网络和底层I/O编程设计&#xff0c;提供了异步操作模型&#xff0c;帮助…...

Saltcorn CLI工具详解:命令行操作与批量处理技巧

Saltcorn CLI工具详解&#xff1a;命令行操作与批量处理技巧 【免费下载链接】saltcorn Free and open source no-code application builder 项目地址: https://gitcode.com/gh_mirrors/sa/saltcorn Saltcorn是一款免费开源的无代码应用构建平台&#xff0c;通过其强大的…...

【AISMM评估避坑指南】:20年SITS专家亲授SITS2026高频失分点与3步合规校准法

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;SITS2026分享&#xff1a;AISMM评估常见问题 在SITS2026会议中&#xff0c;AISMM&#xff08;AI Security Maturity Model&#xff09;评估实践引发广泛关注。许多组织在首次开展评估时&#xff0c;常因…...

互联网大厂 Java 求职面试:在音视频场景中如何使用 Spring Cloud 和 Kafka

互联网大厂 Java 求职面试&#xff1a;在音视频场景中如何使用 Spring Cloud 和 Kafka今天&#xff0c;我们将进入一个有趣的面试场景&#xff0c;面试官是个严肃的技术大牛&#xff0c;而候选人则是搞笑的程序员燕双非。使用音视频场景进行面试问题的探讨。第一轮提问面试官&a…...

基于Markdown的Notion MCP服务器:让AI助手无缝读写知识库

1. 项目概述&#xff1a;当AI助手遇上你的知识库 如果你和我一样&#xff0c;日常重度依赖Notion来管理项目、记录想法、整理文档&#xff0c;同时又希望AI助手&#xff08;比如Claude、Cursor的AI功能&#xff09;能直接帮你操作这些内容&#xff0c;那你可能已经体验过那种“…...

客流统计系统的实现,本质上是一个多模块视觉计算链路

传统方案的问题在于其输入信息单一&#xff0c;只能提供“触发信号”&#xff0c;无法支持行为级分析。因此当前主流实现逐渐转向基于 3D 双目视觉的方案。一、系统架构拆解典型架构分为四层&#xff1a;1. 数据采集层双目摄像头ToF深度传感器RGB Depth同步采集作用&#xff1…...

BBDown终极指南:高效下载B站视频的专业级命令行工具

BBDown终极指南&#xff1a;高效下载B站视频的专业级命令行工具 【免费下载链接】BBDown Bilibili Downloader. 一个命令行式哔哩哔哩下载器. 项目地址: https://gitcode.com/gh_mirrors/bb/BBDown BBDown是一款功能强大的开源命令行工具&#xff0c;专为Bilibili视频下…...

Godot XR Tools:加速VR/AR开发的模块化工具集与实战指南

1. 项目概述&#xff1a;Godot XR Tools 是什么&#xff1f; 如果你正在用 Godot 引擎捣鼓 VR 或 AR 项目&#xff0c;大概率会遇到一些“通用但繁琐”的问题&#xff1a;怎么让虚拟手自然地抓取物体&#xff1f;怎么实现一个稳定可靠的传送移动机制&#xff1f;UI 界面在 3D …...