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

rust宏(macro)详解

前言

rust 学习曲线非常陡峭,但是基本语法也还算挺好理解,自动内存管理有点类似智能指针,基本看一下语法入门就可以大概理解,但是唯独宏很难理解,语法非常晦涩。但是功能非常强大。声明宏类似于c语言的宏处理,但是功能更强大。过程宏则类似于Android的注解编程,自定义AbstractProcessor,但是实现更优雅。
下面记录一下宏处理的一些特点

正文

目前主流的文章都是翻译自官方文档,或者取部分Rust语言圣经,关键的部分特性就确实,只有rust宏详解中非常详细的介绍,这里简要记录一下特点

声明宏

声明宏主要是替代,主要是通过简单的模式匹配,然后进行操作,这貌似非常容易处理面向对象的工厂模式,或者解决方法多参数操作,比如

macro_rules! vec {() => ($crate::__rust_force_expr!($crate::vec::Vec::new()));($elem:expr; $n:expr) => ($crate::__rust_force_expr!($crate::vec::from_elem($elem, $n)));($($x:expr),+ $(,)?) => ($crate::__rust_force_expr!(<[_]>::into_vec(// This rustc_box is not required, but it produces a dramatic improvement in compile// time when constructing arrays with many elements.#[rustc_box]$crate::boxed::Box::new([$($x),+]))));
}

这就是根据不同的匹配模式,=>的前半部分,替换成后半部。比如无参数的vec。因为这是系统接口,这里不在详细介绍操作,只介绍匹配,

  1. ()是无参构造函数。
  2. ($elem:expr; $n:expr)这是匹配模式类似vec![1;5],这是创造一个size是5的,值是1的vec。
  3. 这个是匹配vec![1, 2, 3],这是构造一个内容是1、2、3的vec

第二个匹配模式中,$elem是为匹配的内容命名,方便后面使用,expr(一个表达式 (expression))指明匹配的元素,;就不用解释,就是字面值。$n:expr同样道理。
第二个匹配则稍微复杂一些,这里则用的是循环模式。循环是通过$(....)来指明的,括号内为循环内容,为了方便阅读,则需要有分隔符和循环次数,这里是通过定义分隔符,+定义循环至少一次。$(,)?又是一个循环,循环内容则是。而循环一次则是最多一次。

所有的语句如下:

block:一个块(比如一块语句或者由大括号包围的一个表达式)
expr:一个表达式 (expression)
ident:一个标识符 (identifier),包括关键字 (keywords)
item:一个条目(比如函数、结构体、模块、impl 块)
lifetime:一个生命周期注解(比如 'foo、'static)
literal:一个字面值(比如 “Hello World!”、3.14、‘🦀’)
meta:一个元信息(比如 #[…] 和 #![…] 属性内部的东西)
pat:一个模式 (pattern)
path:一条路径(比如 foo、::std::mem::replace、transmute::<_, int>)
stmt:一条语句 (statement)
tt:单棵标记树
ty:一个类型
vis:一个可能为空的可视标识符(比如 pub、pub(in crate))

循环则如下:

反复捕获的一般形式为 $ ( … ) sep rep。

$ 是字面上的美元符号标记
( … ) 是被反复匹配的模式,由小括号包围。
sep 是可选的分隔标记。它不能是括号或者反复操作符 rep。常用例子有 , 和 ; 。
rep 是必须的重复操作符。当前可以是:

  1. ?:表示最多一次重复,所以此时不能前跟分隔标记。
  2. *:表示零次或多次重复。
  3. +:表示一次或多次重复。

过程宏

分为三类

  • 派生宏(Derive macro):用于结构体(struct)、枚举(enum)、联合(union)类型,可为其实现函数或特征(Trait)。
  • 属性宏(Attribute macro):用在结构体、字段、函数等地方,为其指定属性等功能。如标准库中的#[inline]、#[derive(…)]等都是属性宏。
  • 函数式宏(Function-like macro):用法与普通的规则宏类似,但功能更加强大,可实现任意语法树层面的转换功能。

声明宏需要解析传入的参数,进行匹配,而过程宏则需要自己解析传入的内容,然后进行补充,生成代码。这里需要解析TokenStream,举个例子,就是用宏为一个结构体实现构建者模式。

#[derive(Builder)]
struct Command {// ...
}

最麻烦的是如何实现Builder

#[derive(Builder)]
struct Command {input_paht: String,// ...
}pub fn derive_builder(input: TokenStream) -> TokenStream {let input = parse_macro_input!(input as DeriveInput); // 解析input为 DeriveInput类型let input_ident = input.ident;  // 获取原始类名let ident_builder = format_ident!("{}Builder", input_ident.to_string()); // 拼接builder类名if let Data::Struct(r) = input.data {   // 处理结构体let fields = r.fields;// 结构体属性声明let builder_fields = map_fields(&fields, &mut |(ident, ty)| {quote!(#ident: Option<#ty>,) });// 为builder增加set函数let builder_set_fields = map_fields(&fields, &mut |(ident, ty)| {quote!(pub fn #ident(mut self, value: #ty) -> Self {self.#ident = Some(value);self}) });// 获取builder的属性值let builder_lets = map_fields(&fields, &mut |(ident, _)| {quote!(let #ident = self.#ident.ok_or(format!("field {:?}  not set yet", stringify!(#ident),))?;)});// 初始化时的默认值let builder_fields_values = map_fields(&fields, &mut |(ident, _)| {quote!(#ident,)});quote!(impl #input_ident {pub fn builder() -> #ident_builder {#ident_builder::default()}}#[derive(Default)]pub struct #ident_builder {#builder_fields}impl #ident_builder {#builder_set_fieldspub fn build(self) -> Result<#input_ident, String> {#builder_letsOk(#input_ident{ #builder_fields_values })}}).into()} else {// 不支持非struct类型quote!().into()}
}fn map_fields<F>(fields: &Fields, mapper:&mut F) -> TokenStream2
whereF: FnMut((&Option<proc_macro2::Ident> ,  &Type)) -> TokenStream2,
{let fs = fields.iter().map(|field| mapper((&field.ident ,&field.ty)) );let stream2 = TokenStream2::from_iter(fs);stream2
}

这里为Command实现了builder方法如下:

impl Command{pub fn builder() -> CommandBuilder{CommandBuilder::default()}
}pub struct CommandBuilder{input_path: String,
}impl CommandBuilder{pub fn (mut self, value: String) -> Self {self.input_path = Some(value);self}pub fn build(self) -> Result<Command, String> {let input_path= self.input_path.ok_or(format!("field {:?}  not set yet", stringify!(input_path),))?;Ok(Command{ input_path })}}

属性宏则可以传入参数,让控制更自由一些,这里就不在详细介绍
函数式宏则相对比较简单,类似声明宏,但是可以不去匹配规则,更自由,功能更强大。

解析TokenStream需要依赖一些库,这比较复杂,就不在详细介绍。要结合自己代码需求,慢慢理解。

分析工具
cargo.exe  install cargo-expandcargo.exe expand

后记

rust实在是复杂,这里解释一些语法规则,以后遇到问题再补充。

相关文章:

  • Selenium+Unittest+HTMLTestRunner框架更改为Selenium+Pytest+Allure(一)
  • Sui承诺向流动性质押协议投入$SUI
  • TimescaleDB-1 安装
  • 3D渲染和动画制作软件KeyShot Pro mac附加功能
  • CRM客户管理系统-超详细介绍
  • 机器人视觉
  • OTP语音芯片与可重复擦写(Flash型)语音芯片:特性比较与应用差异
  • CanEasy多场景应用,让汽车总线测试更简单
  • 差分法详解
  • Java集合中的通用算法,开发效率翻倍
  • 【源码】车牌检测+QT界面+附带数据库
  • UE虚幻引擎中程序无需运行也可调试
  • JS的箭头函数this:
  • k8s常用命令及示例(三):apply 、edit、delete
  • java集合之hash算法
  • (十五)java多线程之并发集合ArrayBlockingQueue
  • Angular 响应式表单之下拉框
  • axios 和 cookie 的那些事
  • iOS筛选菜单、分段选择器、导航栏、悬浮窗、转场动画、启动视频等源码
  • LeetCode29.两数相除 JavaScript
  • 第13期 DApp 榜单 :来,吃我这波安利
  • 让你的分享飞起来——极光推出社会化分享组件
  • 实现简单的正则表达式引擎
  • 推荐一个React的管理后台框架
  • 我与Jetbrains的这些年
  • 项目管理碎碎念系列之一:干系人管理
  • 移动互联网+智能运营体系搭建=你家有金矿啊!
  • 基于django的视频点播网站开发-step3-注册登录功能 ...
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • ​【已解决】npm install​卡主不动的情况
  • ​香农与信息论三大定律
  • # 执行时间 统计mysql_一文说尽 MySQL 优化原理
  • #[Composer学习笔记]Part1:安装composer并通过composer创建一个项目
  • $().each和$.each的区别
  • $Django python中使用redis, django中使用(封装了),redis开启事务(管道)
  • (32位汇编 五)mov/add/sub/and/or/xor/not
  • (ZT)一个美国文科博士的YardLife
  • (八)光盘的挂载与解挂、挂载CentOS镜像、rpm安装软件详细学习笔记
  • (二)【Jmeter】专栏实战项目靶场drupal部署
  • (二)springcloud实战之config配置中心
  • (原創) 如何動態建立二維陣列(多維陣列)? (.NET) (C#)
  • (转)Android学习笔记 --- android任务栈和启动模式
  • (转)JAVA中的堆栈
  • (转)负载均衡,回话保持,cookie
  • .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)...
  • .NET Core IdentityServer4实战-开篇介绍与规划
  • .NET 指南:抽象化实现的基类
  • .pop ----remove 删除
  • .sys文件乱码_python vscode输出乱码
  • /var/log/cvslog 太大
  • [Android View] 可绘制形状 (Shape Xml)
  • [C++]命名空间等——喵喵要吃C嘎嘎
  • [HTML]Web前端开发技术29(HTML5、CSS3、JavaScript )JavaScript基础——喵喵画网页
  • [HTTP]HTTP协议的状态码
  • [IE编程] WebBrowser控件中设置页面的缩放