首页 > 其他分享 >Rust宏之derive的设计及实战

Rust宏之derive的设计及实战

时间:2024-10-18 11:25:53浏览次数:1  
标签:derive 宏之 macro pub field ident fn Rust

Rust宏可以极大的简化编写的难度,学习好宏可以更好的减少冗余代码。

宏的基本概念

Rust中的宏可以分为两大类:声明宏(Declarative Macros)和过程宏(Procedural Macros)。

  1. 声明宏:也称为macro_rules!宏,使用macro_rules!关键字定义。它是一种基于模式匹配的文本替换宏,类似于C语言中的宏定义。声明宏在编译期展开,用匹配的代码片段替换宏调用处的代码。
  2. 过程宏:是一种更为高级的宏,它通过编写Rust代码来处理输入的代码,并在编译期间生成新的代码。过程宏主要用于属性宏(Attribute Macros)、类函数宏(Function-Like Macros)和派生宏(Derive Macros)等场景。

宏的实际应用

  1. 声明宏在Rust中的应用,我们最常接触的宏定义vec!或者println!都是标准库里提供的,他可以在编译阶段就进行宏展开,在一定程度上牺牲编译速度有错误及时发现从而保证程序运行稳定。
  2. 过程宏在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中添加

[lib]
proc-macro = true

声明该项目为过程宏项目。

定义宏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中可以用#来序列化局数的变量数据。那么此时我们运行程序,将会输出:

struct name HcluaMacro

类名正确的被打印出来。

字段处理

定义
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,
}
  1. vis表示是否公开,就是表示pub或者pub(super) or pub(crate) or pub(in some::module)或者不公开模式
  2. attrs表示在该字段上的各种属性
  3. mutability表示是否可编辑
  4. 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的数组,我们将用

#(#functions)*

将此部分内容做展开。

完整宏代码:

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方法,符合我们的要求。

注意事项

  1. 学习曲线:难度相对较高,需要理解block,expr, ident, item, literal, pat, path, stmt, tt, ty, vis等相关内容。
  2. 调试难度:由于宏是在编译时执行的,因此调试起来可能比较困难。对于严重依赖调试会相对吃力。
  3. 滥用风险:虽然宏提供了强大的代码生成能力,但滥用宏也可能导致代码难以理解和维护。因此,在使用宏时尽量的做好规划及说明。

标签:derive,宏之,macro,pub,field,ident,fn,Rust
From: https://www.cnblogs.com/wmproxy/p/18473898

相关文章

  • rust学习一、入门之搭建简单开发环境
    最近希望学习一些新的,选择了rust.本篇介绍怎么搭建一个非常简单的windows开发环境,以及如何使用cargo命令1、搭建开发环境(windows11)a.登录官网https://www.rust-lang.org/tools一看就明白,此处略。b.安装rustup一看就明白,此处略。c.安装cargoscriptcargoinstallcargo......
  • Rust环境配置
    docshttps://www.yuque.com/opensource-books/trpl-zh-cnRustnightlybuildusesomefeatureonnightlyortest参考https://www.rust-lang.org/tools/installrustuprunnightlycargobuildRust镜像源exportRUSTUP_DIST_SERVER=http://mirrors.ustc.edu.cn/r......
  • Rust中的macro_rules
    前言在Rust中,macro_rules!是一种声明宏,允许在编译时生成代码。一、打印文本示例:macro_rules!:声明了一个宏,宏的名字是hello。调用hello!()时,编译器在编译时会展开宏,生成println!("Hello,world!");macro_rules!hello{()=>{println!("Hello,world!")......
  • RustRover向Gitlab提价要求证书问题
    源码存储库是Gitlab,并没有使用证书方式进行身份验证,只是用账号和密码进行身份认证,但是,RustRover进行源码操作时候提示要求证书,如下图:解决方法:在插件里面搜索Gitlab,禁用,随后操作源码的时候,输入正确的账号和密码就可以了 参考:https://blog.csdn.net/qq_39306234/article/det......
  • Rust实现单例模式
    前言rust是基于C++实现的,所以对于rust来讲,更适合的是面向过程的设计。不过,仍然可以基于rust采用一些面向对象的设计思想,本文主要讲述如何通过rust语法实现单例模式。一、什么是单例模式?单例模式是一种设计模式,用于确保一个类只有一个实例,并提供一个全局访问点。它常用于需要控......
  • 使用JNA在java中调用rust函数
    JNA是sun公司对JNI能力的封装,更多信息请自行问AI。咱们直接上代码。调用系统本地库先引入jar包:<!--https://mvnrepository.com/artifact/net.java.dev.jna/jna--><dependency><groupId>net.java.dev.jna</groupId><artifactId>jna</artifactId>......
  • Rust 变量和基础类型
    Rust变量和基础数据类型2024-03-13类型,元组,数组,切片,动态数组,生命周期,动态大小,静态大小Rust通过let关键字声明变量,变量默认是不可变(只读)的.letfoo=5;//创建了一个不可变的变量foo,并绑定了5foo=10;//不能对foo重新绑定//使用mut关键字创......
  • Rust 与生成式 AI:从语言选择到开发工具的演进
    在现代软件开发领域,Rust语言正在逐步崭露头角,尤其是在高性能和可靠性要求较高的应用场景。与此同时,生成式AI的崛起正在重新塑造开发者的工作方式,从代码生成到智能调试,生成式AI的应用正成为提升开发效率和质量的重要工具。在本文中,我们将详细探讨Rust语言的现状与趋势,并分析......
  • Rust 中的 HashMap 实战指南:理解与优化技巧
    Rust中的HashMap实战指南:理解与优化技巧在Rust编程中,HashMap是一个强大的键值对数据结构,广泛应用于数据统计、信息存储等场景。在本文中,我们将通过三个实际的代码示例,详细讲解HashMap的基本用法以及如何在真实项目中充分利用它。此外,我们还将探讨Rust的所有权系统对Ha......
  • 实战逆向RUST语言程序
    实战为主,近日2024年羊城杯出了一道Rust编写的题目,这里将会以此题目为例,演示Rust逆向该如何去做。题目名称:sedRust_happyVm题目内容:unhappyrust,happyvm关于Rust逆向,其实就是看汇编,考验选手的基础逆向能力。在汇编代码面前,任何干扰都会成为摆设。1、初步分析64为程序,使用ID......