Rust学习笔记(五十九)宏[通俗易懂]

Rust学习笔记(五十九)宏[通俗易懂]宏macro宏在Rust里是指一组相关特性的集合称谓:使用macro_rules!

欢迎大家来到IT世界,在知识的湖畔探索吧!

宏macro

宏在Rust里是指一组相关特性的集合称谓:

  • 使用macro_rules!构建的声明宏(declarative macro)
  • 3种过程宏
  • 自定义#[derive]宏,用于struct或enum,可以为其指定随derive属性添加的代码
  • 类似属性的宏,可以在任意条目上添加自定义属性
  • 类似于函数的宏,看起来像函数调用,对其指定为参数的token进行操作

macro_rules! 中有一些奇怪的地方。在将来,会有第二种采用 macro 关键字的声明宏,其工作方式类似但修复了这些极端情况。在此之后,macro_rules! 实际上就过时(deprecated)了。在此基础之上,同时鉴于大多数 Rust 程序员 使用 宏而非 编写 宏的事实,此处不再深入探讨 macro_rules!。请查阅在线文档或其他资源,如 “The Little Book of Rust Macros(https://danielkeep.github.io/tlborm/book/index.html)” 来更多地了解如何写宏。

函数与宏的差别

从本质上看,宏是用来编写可以生成其它代码的代码(元编程,metaprograming)。 函数在定义签名时,必须声明参数的个数和类型,而宏可以处理可变的参数。编译器会在解释代码前展开宏。宏的定义比函数复杂得多,难以阅读、理解和维护。在某个文件调用宏时,必须提前定义宏或将宏引入当前作用域。而函数可以在任何位置定义并在任何位置使用。

基于属性来生成代码的过程宏

这种形式更像函数(某种形式的过程)一些。它会接收并操作输入的Rust代码,并生成另外一些Rust代码作为结果。

三种过程宏:

  • 自定义派生
  • 属性宏
  • 函数宏

创建过程宏时,宏定义必须单独放在它们自己的包中,并且使用特殊的包类型。例:

//src/lib.rs
use proc_macro;

#[some_attribute]//some_attribute 是一个使用特定宏的占位符。
pub fn some_name(input: TokenStream) -> TokenStream {//TokenStream 类型由包含在 Rust 中的 proc_macro crate 定义并表示token序列。 这是宏的核心:宏所操作的源代码构成了输入 TokenStream,宏产生的代码是输出 TokenStream。
}

欢迎大家来到IT世界,在知识的湖畔探索吧!

自定义derive宏(派生宏)

需求:

  • 创建一个hello_macro library包,定义一个拥有关联函数hello_macro的HelloMacro trait
  • 我们定义一个能自动实现该trait的过程宏
  • 只需要在对应的类型上标准#[derive(HelloMacro)],就能得到hello_macro函数的默认实现

首先新建并打开文件夹macro_demo,然后新建一个Cargo.toml文件,定义工作空间,内容如下

欢迎大家来到IT世界,在知识的湖畔探索吧![workspace]

members = [
    "hello_macro",
    "hello_macro_derive",
    "pancakes",
]

在macro_demo目录下下分别用命令行执行

> cargo new hello_macro --lib
> cargo new hello_macro_derive --lib
> cargo new pancakes

其中hello_macro是定义HelloMacro trait的包,hello_macro_derive是定义派生宏的包,pancakes是使用派生宏的包。

在hello_macro的lib.rs里定义HelloMacro trait

欢迎大家来到IT世界,在知识的湖畔探索吧!pub trait HelloMacro {
    fn hello_macro();
}

修改hello_macro_derive的Cargo.toml如下,其中两个依赖是用来解析和生成TokenStream的。

[package]
name = "hello_macro_derive"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
proc-macro = true

[dependencies]
syn = "1.0.84"
quote = "1.0.14"

在hello_macro_derive里的lib.rs里创建派生宏如下

extern crate proc_macro;

use crate::proc_macro::TokenStream;
use quote::quote;
use syn;


#[proc_macro_derive(HelloMacro)]
pub fn proc_macro_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_hello_macro(&ast)
}

fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;

    let gen = quote! {
        impl HelloMacro for #name {
            fn hello_macro() {
                println!("Hello, Macro! My name is {}", stringify!(#name));
            }
        }
    };
    gen.into()
}

其中proc_macro是编译器用来读取和操作我们 Rust 代码的 API;syn crate 将字符串中的 Rust 代码解析成为一个可以操作的数据结构;quote 则将 syn 解析的数据结构转换回 Rust 代码。 首先调用syn::parse(input).unwrap()将输入的代码解析为抽象语法树,它的部分结构如下:

DeriveInput {
    // --snip--

    ident: Ident {
        ident: "Pancakes",
        span: #0 bytes(95..103)
    },
    data: Struct(
        DataStruct {
            struct_token: Struct,
            fields: Unit,
            semi_token: Some(
                Semi
            )
        }
    )
}

以上是当我们有一个自定义结构体Pancakes,并在上面标注我们定义的派生宏时解析成的。

然后定义了一个方法impl_hello_macro,读取抽象语法树并生成我们需要的代码。&ast.ident就是被标注该宏的结构体的名字。 quote! 宏让我们可以编写希望返回的 Rust 代码。quote! 宏执行的直接结果并不是编译器所期望的并需要转换为 TokenStream。为此需要调用 into 方法,它会消费这个中间表示(intermediate representation,IR)并返回所需的 TokenStream 类型值。

这个宏也提供了一些非常酷的模板机制;我们可以写 #name ,然后 quote! 会以名为 name 的变量值来替换它。你甚至可以做一些类似常用宏那样的重复代码的工作。 我们期望我们的过程式宏能够为通过 #name 获取到的用户注解类型生成 HelloMacro trait 的实现。该 trait 的实现有一个函数 hello_macro ,其函数体包括了我们期望提供的功能:打印 Hello, Macro! My name is 和注解的类型名。

接着在pancakes包的Cargo.toml里添加我们创建的前两个包为依赖:

[package]
name = "pancakes"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
hello_macro = {path = "../hello_macro"}
hello_macro_derive = {path = "../hello_macro_derive"}

最后在其main.rs里使用自定义的派生宏:

use hello_macro_derive::HelloMacro;
use hello_macro::HelloMacro;

#[derive(HelloMacro)]
struct Pancakes{}

fn main() {
    Pancakes::hello_macro();
}

运行cargo run,会打印Hello, Macro! My name is Pancakes。可以看出我们自定义的宏已经为结构体Pancakes自动实现了HelloMacro trait的关联函数hello_macro。

属性宏

属性宏与自定义derive宏(派生宏)类型:

  • 允许创建新的属性
  • 但是不为derive属性生成代码

属性宏更加灵活:

  • derive宏只能用于结构体和枚举
  • 属性宏可以用于任意条目,如函数

例子,可以创建一个名为 route 的属性用于注解 web 应用程序框架(web application framework)的函数:

#[route(GET, "/")]
fn index() {}

其宏定义的函数签名看起来像这样:

#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {}

其中attr是(GET, “/”),item对应index函数。

类属性宏与自定义派生宏工作方式一致:创建 proc-macro crate 类型的包并实现希望生成代码的函数!

函数宏

函数宏是类似于函数调用的宏,但它比普通函数更加灵活。函数宏可以接收TokenStream作为参数。与上面两种宏一样,在定义中使用Rust代码来操作TokenStream,例如我们想定义一个解析SQL语句的宏:

#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {

上面的宏就可以这么使用sql!()

let sql = sql!(SELECT * FROM posts WHERE id=1);

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/17627.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信