二十、Rust AOP 切面增强
用过 java spring 的同学,应该会对 AspectJ 的 前置、后置、环绕 增强 念念不忘,巧了 rust 也有类似能力,稍显不同的是,为了向 “零成本抽象” 靠齐,Rust 的 “增强” 是在编译期 完成的。
编译期生成,则离不开 “宏”,本篇使用 “过程宏” 来实现 AOP 的代理增强,逻辑比较简单,记录函数的运行时间。
重要的点
- Rust 过程宏,要求放入独立的 “项目” or “包” 中。
- 原因:过程宏必须先被编译才能使用,和项目代码放在一起,也要先编译 “宏”,但 rust 编译单元是 “包”,而无法做到这一点。
创建项目
- 创建一个 lib 项目
cargo new elapsed --lib
- 将项目 描述为 macro(宏)项目
[lib]
proc-macro = true[dependencies]
quote = "1"
syn = { version = "2.0.58", features = ["full"] }
- syn:解析语法树(AST)、及各种语法构成;
- quote:使用 解析结果,生成rust代码,实现想要的功能;
实现“宏增强”逻辑
-
Rust 继续要求:宏声明,必须在 crate root 下,即 lib.rs 中。
但为使结构清晰,可以在 crate root ( src/lib.rs ) 中只做声明,而在其他 mod 中具体实现;
-
elapsed/src/lib.rs
use proc_macro::TokenStream;mod elapsed;#[proc_macro_attribute]
#[cfg(not(test))]
pub fn elapsed(args: TokenStream, func: TokenStream) -> TokenStream {elapsed::elapsed(args, func)
}
- elapsed/src/elapsed.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::ItemFn;
use syn::parse_macro_input;pub(crate) fn elapsed(_attr: TokenStream, func: TokenStream) -> TokenStream {let func = parse_macro_input!(func as ItemFn);let func_vis = &func.vis; // like publet func_block = &func.block; // { some statement or expression here }let func_decl = func.sig;let func_name = &func_decl.ident; // function namelet func_generics = &func_decl.generics;let func_inputs = &func_decl.inputs;let func_output = &func_decl.output;let caller = quote! {#func_vis fn #func_name #func_generics(#func_inputs) #func_output {use std::time;let start = time::Instant::now();#func_blockprintln!("time cost {:?}", start.elapsed());}};caller.into()
}
简单解释:
pub(crate)指定该函数仅在当前crate中可见;parse_macro_input!(func as ItemFn)将 AST Token 转为函数定义func;
随后获取了函数的各个部分:
- vis:可见性;
- block:函数体;
- func.sig:函数签名:
- ident:函数名;
- generics:函数声明的范型;
- inputs:函数入参;
- output:函数出参;
最后,通过 quote! 创建了一个新的 rust 代码块;
caller.into将结果,转换为编译器可识别的内容:TokenStream。
测试运行
- 到另外的工程,引入本项目,并使用该过程宏。
[dependencies]
elapsed = { path = "../elapsed" }
- 本例中,该
#[elapsed]只能加在同步方法上,加在异步方法上,会破坏方法的异步性,导致报错。
#[elapsed]
fn demo(t: u64) {let secs = Duration::from_secs(t);thread::sleep(secs);
}fn main() {demo(4);demo(2);
}
- 运行后,可得:
time cost 4.004699342s
time cost 2.003885116s
- 另
cargo-expand:可查看 方法 经宏展开 替换后的样子
cargo install cargo-expand
cargo expand
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2018::*;
#[macro_use]
extern crate std;
use my_macro::elapsed;
use std::thread;
use std::time::Duration;
fn demo(t: u64) {use std::time;let start = time::Instant::now();{let secs = Duration::from_secs(t);thread::sleep(secs);}{::std::io::_print(::core::fmt::Arguments::new_v1(&["time cost ", "\n"],&[::core::fmt::ArgumentV1::new_debug(&start.elapsed())],),);};
}
fn main() {demo(4);demo(2);
}
总结
通过 syn 和 quote,可以在编译期,操纵整个 rust 代码的 AST 树,为功能编写、甚至框架封装,提供了更多可能 !
完事,拜了个 bye ~
参考资料
- 如何编写一个过程宏(proc-macro)
- Rust过程宏系列教程 | Proc Macro Workshop 之 Builder 实现
- https://github.com/dtolnay/proc-macro-workshop/
- Macro 宏编程
- https://jasonkayzk.github.io/2022/11/25/Rust%E5%8F%8D%E5%B0%84%E4%B9%8B%E8%BF%87%E7%A8%8B%E5%AE%8F/
相关文章:
二十、Rust AOP 切面增强
用过 java spring 的同学,应该会对 AspectJ 的 前置、后置、环绕 增强 念念不忘,巧了 rust 也有类似能力,稍显不同的是,为了向 “零成本抽象” 靠齐,Rust 的 “增强” 是在编译期 完成的。 编译期生成,则离…...
掌握Go语言:Go语言精细错误,清晰、高效的错误处理实践(32)
错误处理是任何编程语言中都至关重要的一部分,Go 语言提供了一套简单而强大的错误处理机制,使得处理错误变得高效而清晰。 Go 错误类型 在 Go 中,错误是一个普通的接口类型,即 error 接口,其定义如下: t…...
Spring与Web环境的集成
1. ApplicationContext应用上下文获取方式 应用上下文对象是通过new ClasspathXmlApplicationContext(spring配置文件) 方式获取的,但是每次从容器中获得Bean时都要编写new ClasspathXmlApplicationContext(spring配置文件) ,这样的弊端是配置文件加载多…...
二叉树的遍历——bfs广度优先搜索
1、BinNode类的创建 (1)代码总览 ##(2)测试示例 2、二叉树的遍历 (1)图示 (2)代码总览 (3)测试示例...
飞鸟写作可靠吗 #职场发展#经验分享#经验分享
飞鸟写作是一个非常便捷的论文写作工具,不仅可以帮助用户高效地完成论文写作,还可以提供查重降重的功能,帮助用户确保论文的原创性。那么,飞鸟写作到底可靠吗?答案是肯定的。 首先,飞鸟写作提供的查重降重功…...
Java 实现自定义注解
一、interface 关键字 我们想定义一个自己的注解 需要使用 interface 关键字来定义。 如定义一个叫 MyAnnotation 的注解: public interface MyAnnotation { } 二、元注解 光加上 interface 关键字 还不够,我们还需要了解5大元注解 RetentionTargetDo…...
代码随想录Day48
Day 48 动态规划part09 今日任务 198.打家劫舍213.打家劫舍II337.打家劫舍III 代码实现 基础打家劫舍 class Solution {public static int rob(int[] nums) {if (nums null || nums.length 0) return 0;if (nums.length 1) return nums[0];int[] dp new int[nums.leng…...
Web 后台项目,权限如何定义、设置、使用:菜单权限、按钮权限 ts element-ui-Plus
Web 后台项目,权限如何定义、设置、使用:菜单权限、按钮权限 ts element-ui-Plus 做一个后台管理项目,里面需要用到权限管理。这里说一下权限定义的大概,代码不多,主要讲原理和如何实现它。 一、权限管理的原理 权限…...
ADB 操作命令及其详细用法
adb devices 用途:列出连接到计算机的所有 Android 设备。详解:执行该命令后,ADB 将扫描连接到计算机的所有 Android 设备,并列出它们的序列号。 adb connect <device> 用途:连接到指定 IP 地址的 Android 设备。…...
类的函数成员(三):拷贝构造函数
一.什么是拷贝构造函数? 1.1 概念 同一个类的对象在内存中有完全相同的结构,如果作为一个整体进行复制或称拷贝是完全可行的。这个拷贝过程只需要拷贝数据成员,而函数成员是共用的(只有一份拷贝)。 在建立对象…...
C#操作MySQL从入门到精通(8)——对查询数据进行高级过滤
前言 我们在查询数据库中数据的时候,有时候需要剔除一些我们不想要的数据,这时候就需要对数据进行过滤,比如学生信息中,我只需要年龄等于18的,同时又要家乡地址是安徽的,类似这种操作专栏第7篇的C#操作MySQL从入门到精通(7)——对查询数据进行简单过滤简单过滤方法就无法…...
Centos 7 安装通过yum安装google浏览器
在CentOS 7上使用yum安装Google Chrome浏览器稍微复杂一些,因为Chrome并不直接包含在默认的Yum仓库中。按照以下步骤来操作: 1、添加Google Chrome仓库 首先,您需要手动添加Google Chrome的Yum仓库。打开终端,并使用文本编辑器&a…...
题目:学习使用按位与 。
题目:学习使用按位与 & 。 There is no nutrition in the blog content. After reading it, you will not only suffer from malnutrition, but also impotence. The blog content is all parallel goods. Those who are worried about being cheated shoul…...
逐步分解,一文教会你如何用 jenkins+docker 实现主从模式
jenkins 主从模式想必大家都不陌生,大家在学习过程中为了学习方便都在自己本地搭建了 jenkins 环境,然后通过 javaweb 方式实现,对于 docker 下实现主从模式大家好像兴趣挺大。 今天就通过这篇文章给大家讲讲怎么玩,希望对大家有…...
WebSocket 对于手游的意义
WebSocket作为一个HTTP的升级协议,其实对HTTP协议用的不多,主要是消息头相关部分,WebScoket协议最初的动机应该是给网页应用增加一个更贴近实时环境的通讯方式,让某些网页应用得到更佳的通讯质量(双工,低延…...
安卓APP的技术质量:如何提高
安卓APP的技术质量:如何提高 技术质量包括稳定性和性能,还有资源工具化程序.你的APP 的技术质量能够影响你的用户体验.一个高质量的体验不仅 最小化了技术问题的存在,而且也最大化地利用了安卓操作 系统和设备硬件的能力. 为了构建一个高质量的APP,遵循如下的指导原则: 形式因…...
二分查找 -- 力扣(LeetCode)第704题
题目 https://leetcode.cn/problems/binary-search/description/ 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 示例…...
Windows下如何确定虚函数在虚函数表中的位置
我需要用c#调用 c 的 类的函数, 虽然可以通过头文件的顺序,但是如果可以打印出虚函数在虚表中的Offset更好。 测试要求: Windows, x86 只有1层虚函数,没有被override过 虚函数调用如下 auto a_reqCreditDetail &XtTraderApi::reqCreditDetail; (a…...
C++设计模式:观察者模式(三)
1、定义与动机 观察者模式定义:定义对象间的一种1对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生比改变时,所有依赖于它的对象都得到通知并且自动更新 再软件构建过程中,…...
CentOS运行Py脚本报错illegal instruction故障处理
测试Python脚本运行环境及依赖 [root@localhost network]# python3 devops_ping_test1.py Illegal instruction ①、illegal instruction报错 由于本人第一次测试时运行是正常的,但是在测试过程中多次修改、覆盖代码运行后提示Illegal instruction(非法指令),所以不能单…...
23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...
多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
