首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Rust 过程宏(Procedural Macros)基础

Rust 过程宏(Procedural Macros)基础

原创
作者头像
8菠萝
修改2021-06-10 11:46:46
修改2021-06-10 11:46:46
3.4K0
举报
文章被收录于专栏:菠萝上市没有菠萝上市没有

概念

宏的作用就是在编译期间对原代码进行扩展,实现目标功能。简单的说宏就是生成代码的代码。

官方解释:

You can sort of think of procedural macros as functions from an AST(抽象语法树) to another AST.

The Rust Reference

(你可以简单认为,过程宏是一个将原有AST语法树转换为另外一个AST语法树的函数)

个人理解,Rust 宏相比C++中的宏定义, 它提供了一种可用让开发人员更容易介入代码编译过程的入口

Rust 过程宏定义分三种

  1. #[proc_macro] 函数似宏

和macro_rules! 功能类似,但更为强大。

2. #[proc_macro_derive(Name)] 派生宏

用于结构体(struct)、枚举(enum)、联合(union)类型,可为其实现函数或特征(Trait)

3. #[proc_macro_attribute] 属性宏

用于属性宏, 用在结构体、字段、函数等地方,为其指定属性等功能

其中 Derive Macro 派生宏和 Attribute Marco 宏的区别是:派生宏生成代码,然后将其添加到同一模块中,而属性宏生成代码来替换它们所应用的项目。 派生宏用于扩展, 属性宏用于替换。后面在例子中说明。

实践

  1. cargo new custom 新建一个名为custom的工程。
  2. cd custom && cargo new custom-derive 在custom内新建一个名为custom-derive 用于编写过程宏。

custom

Cargo.toml

代码语言:javascript
复制
[package]
name = "custom"
version = "0.1.0"
[dependencies]
custom-derive={path="custom-derive"}

custom-derive

Cargo.toml

代码语言:javascript
复制
[package]
name="custom-derive"
version="0.1.0"

[lib]
proc-macro = true   # 使用过程宏

[dependencies]
# quote = "1.0.9"                 # 目前没用到,先注释了
# proc-macro2 = "1.0.27" 
# syn = {version="1.0.72", features=["full"]}

目录结构
目录结构

3. lib.rs

代码语言:javascript
复制
use proc_macro::TokenStream;

extern crate proc_macro;

// 函数式宏
#[proc_macro]
pub fn make_hello(item: TokenStream) -> TokenStream {
    let name = item.to_string();
    let hell = "Hello ".to_string() + name.as_ref();
    let fn_name =
        "fn hello_".to_string() + name.as_ref() + "(){ println!(\"" + hell.as_ref() + "\"); }";
    fn_name.parse().unwrap()
}

// 属性宏 (两个参数)
#[proc_macro_attribute]
pub fn log_attr(attr:TokenStream, item:TokenStream)->TokenStream{
    println!("Attr:{}", attr.to_string());
    println!("Item:{}", item.to_string());
    item
}


// 派生宏
#[proc_macro_derive(Hello)]
pub fn hello_derive(input: TokenStream)-> TokenStream {
    println!("{:?}", input);
    TokenStream::new()  
    // 如果直接返回input,编译会报重复定义,说明派生宏用于扩展定义
    // input   
}

TokenStream 相当编译过程中的语法树的流。

4. main.rs

代码语言:javascript
复制
extern crate custom_derive;
use custom_derive::log_attr;
use custom_derive::make_hello;
use custom_derive::Hello;

make_hello!(world);
make_hello!(张三);

#[log_attr(struct, "world")]
struct Hello{
    pub name: String,
}

#[log_attr(func, "test")]
fn invoked(){}


#[derive(Hello)]
struct World;

fn main() {
    // 使用make_hello生成
    hello_world();
    hello_张三();
}

make_hello 使用#[proc_macro]

定义自动生成一个传入参数函数。

Hello world

Hello 张三

log_attr 使用#[proc_macro_attribute]

编译期间会打印结构类型和参数,后面可用修改替换原属性定义。

Attr:struct, "world"

Item:struct Hello { pub name : String, }

Attr:func, "test"

Item:fn invoked() { }

#[derive(Hello)] 使用#[proc_macro_derive(Hello)]·

会打印当前TokenStream 结点流,后面可以合 syn 与 quto 库结合,扩展定义。

TokenStream [Ident { ident: "struct", span: #0 bytes(286..292) }, Ident { ident: "World", span: #0 bytes(293..298) }, Punct { ch: ';', spacing: Alone, span: #0 bytes(298..299) }]

后续: syn 与 quto 使用。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概念
  • 实践
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档