二十、Rust AOP 切面增强

news/2024/5/14 16:42:47

  用过 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/

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

书生·浦语大模型全链路开源体系-第2课

书生浦语大模型全链路开源体系-第2课 书生浦语大模型全链路开源体系-第2课相关资源实战部署InternLM2-Chat-1.8B模型准备环境下载模型运行案例 实战部署InternLM2-Chat-7B模型准备环境下载模型及案例代码运行cli案例代码运行web案例代码配置SSH公钥信息配置SHH隧道连接 熟悉 Hu…

【THM】Protocols and Servers 2(协议和服务器 2

介绍 协议和服务器房间涵盖了许多协议: 远程登录HTTP协议文件传输协议邮件传输协议POP3IMAP实现这些协议的服务器会受到不同类型的攻击。仅举几例,请考虑: 嗅探攻击(网络数据包捕获)中间人 ( MITM ) 攻击密码攻击(身份验证攻击)漏洞从安全的角度来看,我们始终需要思考…

【项目新功能开发篇】开发编码

作者介绍:本人笔名姑苏老陈,从事JAVA开发工作十多年了,带过大学刚毕业的实习生,也带过技术团队。最近有个朋友的表弟,马上要大学毕业了,想从事JAVA开发工作,但不知道从何处入手。于是&#xff0…

MySQL的基本操作(超详细)

👨‍💻作者简介:👨🏻‍🎓告别,今天 📔高质量专栏 :☕java趣味之旅 📔(零基础)专栏:MSQL数据库 欢迎🙏点赞&…

用html写一个爱心

<!DOCTYPE html> <html lang"zh-CN"><head><meta http-equiv"Content-Type" content"text/html; charsetUTF-8" /><title>爱您</title><style>* {padding: 0;margin: 0;}body {background-color: pin…

【JAVAEE学习】探究Java中多线程的使用和重点及考点

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…