Rust宏可以极大的简化编写的难度,学习好宏可以更好的减少冗余代码。
宏的基本概念
Rust中的宏可以分为两大类:声明宏(Declarative Macros)和过程宏(Procedural Macros)。
- 声明宏:也称为macro_rules!宏,使用macro_rules!关键字界说。它是一种基于模式匹配的文本替换宏,雷同于C语言中的宏界说。声明宏在编译期展开,用匹配的代码片段替换宏调用处的代码。
- 过程宏:是一种更为高级的宏,它通过编写Rust代码来处理输入的代码,并在编译期间生成新的代码。过程宏重要用于属性宏(Attribute Macros)、类函数宏(Function-Like Macros)和派生宏(Derive Macros)等场景。
宏的现实应用
- 声明宏在Rust中的应用,我们最常接触的宏界说vec!大概println!都是尺度库里提供的,他可以在编译阶段就进行宏展开,在一定水平上牺牲编译速率有错误实时发现从而保证步伐运行稳定。
- 过程宏在Rust中也是极为常见,就好比某个类,我们必要clone方法,但是声明的类并不支持clone,那么我们就可以在此类声明derive(Clone)如果必要默认的构造方法,那么同样可以声明derive(Default)
- #[derive(Clone, Default)]
- struct HcluaMacro {
- field: u32,
- }
复制代码 此时我们就可以使用:- let obj = HcluaMacro::default();
- let obj_clone = obj.clone();
复制代码 雷同的还要在序列化的宏等。
过程宏的实战
目录为Rust中的lua库hclua做对象的绑定,可以快速的实现Rust对象在Lua中的快速使用绑定。
新建库
由于过程宏只能在单独的库中使用,所以此时我们必要新建单独的一个项目cargo new hclua-macro,并在新项目的Cargo.toml中添加声明该项目为过程宏项目。
界说宏ObjectMacro
起首我们得界说ObjectMacro宏,那么我们必要声明:- #[proc_macro_derive(ObjectMacro)]
- pub fn object_macro_derive(input: TokenStream) -> TokenStream {
- TokenStream::new()
- }
复制代码 此处我们就可以在这基础上实现额外的代码,他将在声明该宏文件中自动添加代码。
我们做以下测试:- #[proc_macro_derive(ObjectMacro)]
- pub fn object_macro_derive(input: TokenStream) -> TokenStream {
- quote! {
- fn this_is_macro_auto() {
- println!("this_is_macro_auto auto func");
- }
- }.into()
- }
复制代码 其中quote!可以快速的生成代码块。
展开宏cargo-expand
接下我们必要宏在这个过期中帮我们生成了什么,我们借助以下工具cargo-expand,通过cargo install cargo-expand进行安装。
此时用cargo expand可以发现宏展开后的代码如下:- #![feature(prelude_import)]
- #[prelude_import]
- use std::prelude::rust_2021::*;
- #[macro_use]
- extern crate std;
- use hclua_macro::ObjectMacro;
- struct HcluaMacro {
- field: u32,
- }
- fn this_is_macro_auto() {
- {
- ::std::io::_print(format_args!("this_is_macro_auto auto func\n"));
- };
- }
- fn main() {
- this_is_macro_auto();
- }
复制代码 此时我们并没有处理跟类相关的任何东西,我们可以用parse_macro_input!将输入转成ItemStruct大概DeriveInput- #[proc_macro_derive(ObjectMacro)]
- pub fn object_macro_derive(input: TokenStream) -> TokenStream {
- let ItemStruct {
- ident,
- fields,
- attrs,
- ..
- } = parse_macro_input!(input);
- let name = ident.to_string();
- quote! {
- fn this_is_macro_auto() {
- println!("struct name {}", #name);
- }
- }.into()
- }
复制代码 在quote中可以用#来序列化局数的变量数据。那么此时我们运行步伐,将会输出:类名正确的被打印出来。
字段处理
界说
- pub struct Field {
- pub attrs: Vec<Attribute>,
- pub vis: Visibility,
- pub mutability: FieldMutability,
- pub ident: Option<Ident>,
- pub colon_token: Option<Token![:]>,
- pub ty: Type,
- }
复制代码
- vis表现是否公开,就是表现pub大概pub(super) or pub(crate) or pub(in some::module)大概不公开模式
- attrs表现在该字段上的各种属性
- mutability表现是否可编辑
- ident变量的名字,当enum时只有类型没著名字
我们就可以通过处理变量的各种情况然后进行操纵,好比添加get_#ident大概set_#ident等方法。
属性处理
在此处我们界说了两种属性名称,hclua_field及 hclua_cfg,一种设置名称,一种设置是否可以在Lua中直接访问的字段名称,此时的宏界说:- #[proc_macro_derive(ObjectMacro, attributes(hclua_field, hclua_cfg))]
- pub fn object_macro_derive(input: TokenStream) -> TokenStream {
-
- }
复制代码 如果没有在此处界说的attrib,在类型里直接添加会报编译错误。
此处我们判断是否为hclua_field字段进行相应的加工。- let functions: Vec<_> = fields
- .iter()
- .map(|field| {
- let field_ident = field.ident.clone().unwrap();
- if field.attrs.iter().any(|attr| attr.path().is_ident("hclua_field")) {
- quote! {}
- } else {
- quote! {}
- }
- })
- .collect();
复制代码 接下来将自动实现get及set方法。此处functions为TokenStream的数组,我们将用将此部门内容做展开。
完整宏代码:- use proc_macro::TokenStream;
- use quote::{format_ident, quote};
- use syn::{self, ItemStruct};
- use syn::parse_macro_input;
- #[proc_macro_derive(ObjectMacro, attributes(hclua_field, hclua_cfg))]
- pub fn object_macro_derive(input: TokenStream) -> TokenStream {
- let ItemStruct {
- ident,
- fields,
- attrs,
- ..
- } = parse_macro_input!(input);
- let functions: Vec<_> = fields
- .iter()
- .map(|field| {
- let field_ident = field.ident.clone().unwrap();
- if field.attrs.iter().any(|attr| attr.path().is_ident("hclua_field")) {
- let get_name = format_ident!("get_{}", field_ident);
- let set_name = format_ident!("set_{}", field_ident);
- let ty = field.ty.clone();
- quote! {
- fn #get_name(&mut self) -> &#ty {
- &self.#field_ident
- }
- fn #set_name(&mut self, val: #ty) {
- self.#field_ident = val;
- }
- }
- } else {
- quote! {}
- }
- })
- .collect();
- let name = ident.to_string();
- quote! {
- fn this_is_macro_auto() {
- println!("struct name {}", #name);
- }
- impl #ident {
- #(#functions)*
- }
- }.into()
- }
复制代码 将示例代码进行如下书写:- use hclua_macro::ObjectMacro;
- #[derive(ObjectMacro)]
- struct HcluaMacro {
- #[hclua_field]
- field: u32,
- not_field: u32,
- }
- fn main() {
- this_is_macro_auto();
- }
复制代码 通过cargo expand将得到如下的代码:- #![feature(prelude_import)]
- #[prelude_import]
- use std::prelude::rust_2021::*;
- #[macro_use]
- extern crate std;
- use hclua_macro::ObjectMacro;
- struct HcluaMacro {
- #[hclua_field]
- field: u32,
- not_field: u32,
- }
- fn this_is_macro_auto() {
- {
- ::std::io::_print(format_args!("struct name {0}\n", "HcluaMacro"));
- };
- }
- impl HcluaMacro {
- fn get_field(&mut self) -> &u32 {
- &self.field
- }
- fn set_field(&mut self, val: u32) {
- self.field = val;
- }
- }
- fn main() {
- this_is_macro_auto();
- }
复制代码 自动实现了get及set方法,符合我们的要求。
注意事项
- 学习曲线:难度相对较高,必要理解block,expr, ident, item, literal, pat, path, stmt, tt, ty, vis等相关内容。
- 调试难度:由于宏是在编译时实行的,因此调试起来可能比力困难。对于严重依赖调试会相对吃力。
- 滥用风险:虽然宏提供了强大的代码生成能力,但滥用宏也可能导致代码难以理解和维护。因此,在使用宏时尽量的做好规划及说明。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |