Rust 的四大类型的宏 (元编程)

这篇具有很好参考价值的文章主要介绍了Rust 的四大类型的宏 (元编程)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

概念

Rust 的宏体系:主要分为声明式函数宏 Function Macro过程宏(Procedural Macro),其中过程宏 (proc_macro)又有类函数的过程宏(Function-like-procedural-macros)、过程属性宏(Attribute Macro)和过程派生宏(Derive Macro)。而属性宏(Attribute Macro)和派生宏(Derive Macro)是两个比较常见常用的类型。他们各自具有不同的特性和用途,可以根据需要选择合适的宏类型来实现特定的代码生成或元编程需求。

过程宏允许在编译时运行对 Rust 句法进行操作的代码,它可以在消费掉一些 Rust 句法输入的同时产生新的 Rust 句法输出。可以将过程宏想象成是从一个 AST 到另一个 AST 的函数映射。

过程宏有两种报告错误的方法。首先是 panic;第二个是发布 compile_error 性质的宏调用。

函数宏或声明宏(Function Macro)

函数宏是一种宏,它接受输入并生成输出。它们可以像函数一样接受参数,并使用类似于宏的语法进行处理和转换。函数宏使用 macro_rules! 关键字定义,并使用 ! 符号调用。

例如,在下面的示例中,我们定义了一个简单的函数宏 hello_macro!,它接受一个参数并生成一个打印语句:

   macro_rules! hello_macro {
       ($name:expr) => {
           println!("Hello, {}!", $name);
       };
   }

   fn main() {
       hello_macro!("World"); // 输出:Hello, World!
   }

过程宏(Procedural Macro)

过程宏是一种更强大和灵活的宏,它允许在编译时根据 Rust 代码的结构进行更复杂的代码生成和转换。过程宏是通过创建一个实现特定 trait 的自定义宏来定义的。这些过程宏可以用于生成代码、属性处理、代码转换和其他元编程任务。

另外,Rust 标准库中的 proc-macro 模块提供了用于编写自定义过程宏的相关类型和函数,例如 TokenStreamTokenTree 等。

类函数的过程宏(Function-like-procedural-macros)

类函数过程宏是使用宏调用运算符(!)调用的过程宏。

这种宏是由一个带有 proc_macro属性 和 (TokenStream) -> TokenStream 签名的 公有可见性函数定义。输入 TokenStream 是由宏调用的定界符界定的内容,输出 TokenStream 将替换整个宏调用。

例如,下面的宏定义忽略它的输入,并将函数 answer 输出到它的作用域。

#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;

#[proc_macro]
pub fn make_answer(_item: TokenStream) -> TokenStream {
    "fn answer() -> u32 { 42 }".parse().unwrap()
}

然后我们用它在一个二进制 crate 里打印 “42” 到标准输出。

extern crate proc_macro_examples;
use proc_macro_examples::make_answer;

make_answer!();

fn main() {
    println!("{}", answer());
}

派生宏(Derive Macro)

派生宏是一种特殊类型的宏,它用于根据用户自定义的类型自动生成代码。派生宏通常用于为结构体、枚举或 trait 实现自动生成常见的实现代码。

自定义派生宏由带有 proc_macro_derive属性和 (TokenStream) -> TokenStream签名的公有可见性函数定义。

派生宏使用 #[derive(...)] 语法,并放置在类型定义上方。它们可以自动为类型实现特定的 trait 或生成相关的代码。

例如,使用 #[derive(Debug)] 可以自动生成针对调试输出的实现,#[derive(Clone, Copy)] 可以自动生成克隆和复制的实现。

用户也可以通过编写自己的派生宏来自定义生成的代码,例如,实现自定义的序列化、反序列化逻辑或其他定制行为。

需要注意的是,属性宏和派生宏都是 Rust 中的过程宏(Procedural Macro)的一种。它们通过自定义宏来扩展或生成代码,提供了更大的灵活性和元编程能力,可以根据需要修改或生成 Rust 代码的结构和行为。

派生宏附加其他属性

派生宏可以将额外的属性添加到它们所在的程序项的作用域中。这些属性被称为派生宏辅助属性。这些属性是惰性的,它们存在的唯一目的是将这些属性在使用现场获得的属性值反向输入到定义它们的派生宏中。也就是说所有该宏的宏应用都可以看到它们。

关于活跃属性和惰性属性:属性要么是活跃的,要么是惰性的。在属性处理过程中,活跃属性将自己从它们所在的对象上移除,而惰性属性依然保持原位置不变。

cfgcfg_attr 属性是活跃的。test属性在为测试所做的编译形式中是惰性的,在其他编译形式中是活跃的。宏属性是活跃的。所有其他属性都是惰性的。

定义辅助属性的方法是在 proc_macro_derive 宏中放置一个 attributes 键,此键带有一个使用逗号分隔的标识符列表,这些标识符是辅助属性的名称。

例如,下面的派生宏定义了一个辅助属性 helper,但最终没有用它做任何事情。

#![crate_type="proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;

#[proc_macro_derive(HelperAttr, attributes(helper))]
pub fn derive_helper_attr(_item: TokenStream) -> TokenStream {
    TokenStream::new()
}

然后在一个结构体上使用这个派生宏:

#[derive(HelperAttr)]
struct Struct {
    #[helper] field: ()
}

派生宏示例一:自定义的派生宏

下面是派生宏的一个示例。它没有对输入执行任何有用的操作,只是追加了一个函数 answer

#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;

#[proc_macro_derive(AnswerFn)]
pub fn derive_answer_fn(_item: TokenStream) -> TokenStream {
    "fn answer() -> u32 { 42 }".parse().unwrap()
}

然后使用这个派生宏:

extern crate proc_macro_examples;
use proc_macro_examples::AnswerFn;

#[derive(AnswerFn)]
struct Struct;

fn main() {
    assert_eq!(42, answer());
}

派生宏示例二:派生系统内建的宏

// 定义一个派生宏 HelloDebug,为结构体自动生成 Debug trait 的实现
#[derive(Debug)]
struct HelloDebug {
    name: String,
}

fn main() {
    let hello = HelloDebug {
        name: "World".to_string(),
    };

    println!("{:?}", hello); // 输出:HelloDebug { name: "World" }
}

在上述示例中,我们定义了一个名为 HelloDebug 的结构体,并为其应用了派生宏 #[derive(Debug)]。这将自动生成 Debug trait 的实现,使我们能够使用 println! 宏打印出结构体的调试信息。在 main 函数中,我们创建了一个 HelloDebug 实例,并通过 println! 打印出结构体的调试信息。

派生宏示例三:派生第三方定义的宏

// 定义一个派生宏 Builder,为结构体自动生成 builder 模式的代码
#[derive(Builder)]
struct Person {
    name: String,
    age: u32,
    address: String,
}

fn main() {
    let person = Person::new()
        .name("John")
        .age(30)
        .address("123 Street")
        .build();

    println!("{:?}", person);
}

在上述示例中,我们使用派生宏 #[derive(Builder)] 为结构体 Person 自动生成 builder 模式的代码。这使我们能够使用链式调用的方式创建 Person 实例,并在 build 方法中构建最终的对象。在 main 函数中,我们使用 builder 模式创建了一个 Person 实例,并打印出其信息。

这是一个更复杂的示例,展示了派生宏可以用于生成更多的代码,例如构建器模式、序列化和反序列化的代码等。

属性宏(Attribute Macro)

属性宏是一种基于属性的宏,用于修改、扩展或注解 Rust 代码。它们通常用于为函数、结构体、枚举、模块等添加元数据或自定义行为。

属性宏使用 #[...] 语法,可以应用于各种语法结构,例如函数、结构体等。它们可以接收属性中的参数,并根据需要对代码进行转换、生成额外的代码或执行其他逻辑。当出现 #![...] 时候,表示该属性应用于当前模版。

属性宏由带有 proc_macro_attribute属性和 (TokenStream, TokenStream) -> TokenStream签名的公有可见性函数定义。签名中的第一个 TokenStream 是属性名称后面的定界 token树。如果该属性作为裸属性(bare attribute)给出,则第一个 TokenStream 值为空。第二个 TokenStream 是程序项的其余部分,包括该程序项的其他属性。

示例

当谈到属性宏和派生宏时,以下是在 Rust 中的代码示例:

属性宏示例一:

例如,下面这个属性宏接受输入流并按原样返回,实际上对属性并无操作。

#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;

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

下面示例显示了属性宏看到的字符串化的 TokenStream。输出将显示在编译时的编译器输出窗口中。(具体格式是以 "out:"为前缀的)输出内容也都在后面每个示例函数后面的注释中给出了。

// my-macro/src/lib.rs
extern crate proc_macro;
use proc_macro::TokenStream;

#[proc_macro_attribute]
pub fn show_streams(attr: TokenStream, item: TokenStream) -> TokenStream {
    println!("attr: \"{}\"", attr.to_string());
    println!("item: \"{}\"", item.to_string());
    item
}

下面示例显示了属性宏看到的字符串化的 TokenStream。输出将显示在编译时的编译器输出窗口中。(具体格式是以 "out:"为前缀的)输出内容也都在后面每个示例函数后面的注释中给出了。

// src/lib.rs
extern crate my_macro;

use my_macro::show_streams;

// 示例: 基础函数
#[show_streams]
fn invoke1() {}
// out: attr: ""
// out: item: "fn invoke1() { }"

// 示例: 带输入参数的属性
#[show_streams(bar)]
fn invoke2() {}
// out: attr: "bar"
// out: item: "fn invoke2() {}"

// 示例: 输入参数中有多个 token 的
#[show_streams(multiple => tokens)]
fn invoke3() {}
// out: attr: "multiple => tokens"
// out: item: "fn invoke3() {}"

// 示例:
#[show_streams { delimiters }]
fn invoke4() {}
// out: attr: "delimiters"
// out: item: "fn invoke4() {}"

属性宏示例二:

// 定义一个属性宏 hello_attribute,将传入的字符串包装在 println! 宏中
#[proc_macro_attribute]
pub fn hello_attribute(attr: TokenStream, input1: TokenStream) -> TokenStream {
    let output = format!("println!(\"Hello, {}\");", attr.to_string());
    println!("{}", output);
    input1
}

// 使用 hello_attribute 宏为函数添加注解
#[hello_attribute("World")]
fn greet() {
    // 生成的代码将打印 "Hello, World"
}

在上述示例中,我们定义了一个名为 hello_attribute 的属性宏,它将传入的字符串包装在 println! 宏中。然后,我们使用 hello_attribute 宏对 greet 函数进行注解,在运行时将打印 “Hello, World”。

当涉及到更复杂的示例时,属性宏和派生宏可以实现更多的功能和代码转换。以下是更复杂一点的示例:

属性宏示例三:

// 定义一个属性宏 repeat,将函数体重复执行指定次数
#[proc_macro_attribute]
pub fn repeat(attr: TokenStream, input: TokenStream) -> TokenStream {
    let repeat_count = attr.to_string().parse::<u32>().unwrap();
    let input_fn = input.to_string();

    let mut output = TokenStream::new();
    if let Some(index) = input_fn.find('(') {
        for i in 0..repeat_count {
            let ret = format!("{}{}{}", &input_fn[..index], i+1, &input_fn[index..]);
            println!("fn: \"{}\"", ret);
            output.extend(ret.parse::<TokenStream>().unwrap());
        }
    }

    output
}

// 使用 repeat 宏为函数添加注解,使函数体重复执行 3 次
#[repeat(3)]
fn greet() {
    println!("Hello, world!");
}

在上述示例中,我们定义了一个名为 repeat 的属性宏。该宏接受一个参数 attr,表示重复执行的次数,并将函数体 input 重复执行指定次数。在 greet 函数上方使用 #[repeat(3)] 注解,将函数体定义了 3 个类似的函数。文章来源地址https://www.toymoban.com/news/detail-672397.html

到了这里,关于Rust 的四大类型的宏 (元编程)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Rust编程语言入门之无畏并发

    Concurrent:程序的不同部分之间独立的执行(并发) Parallel:程序的不同部分同时运行(并行) Rust无畏并发:允许你编写没有细微Bug的代码,并在不引入新Bug的情况下易于重构 注意:本文中的”并发“泛指 concurrent 和 parallel 在大部分OS里,代码运行在进程(process)中,OS同时

    2023年04月19日
    浏览(68)
  • Rust编程语言入门之智能指针

    指针:一个变量在内存中包含的是一个地址(指向其它数据) Rust 中最常见的指针就是”引用“ 引用: 使用 借用它指向的值 没有其余开销 最常见的指针类型 智能指针是这样一些数据结构: 行为和指针相似 有额外的元数据和功能 通过记录所有者的数量,使一份数据被多个

    2023年04月16日
    浏览(49)
  • Rust编程语言入门之高级特性

    不安全 Rust 高级 Trait 高级 类型 高级函数和闭包 宏 隐藏着第二个语言,它没有强制内存安全保证:Unsafe Rust(不安全的 Rust) 和普通的 Rust 一样,但提供了额外的“超能力” Unsafe Rust 存在的原因: 静态分析是保守的。 使用 Unsafe Rust:我知道自己在做什么,并承担相应风险

    2023年04月24日
    浏览(49)
  • Rust编程语言入门之模式匹配

    模式是Rust中的一种特殊语法,用于匹配复杂和简单类型的结构 将模式与匹配表达式和其他构造结合使用,可以更好地控制程序的控制流 模式由以下元素(的一些组合)组成: 字面值 解构的数组、enum、struct 和 tuple 变量 通配符 占位符 想要使用模式,需要将其与某个值进行

    2023年04月22日
    浏览(91)
  • 如何在 macOS 上安装 Rust 编程语言

    安装Rust编程语言在Mac上是一个相对简单的过程,但它可能会涉及多个步骤。在本文中,我将详细说明如何在Mac上安装Rust,并提供一些常见问题的解决方法。请注意,由于软件和工具可能会发生变化,因此建议首先查看Rust官方网站以获取最新的安装说明。 目录 1.打开终端 2

    2024年02月01日
    浏览(55)
  • 【跟小嘉学 Rust 编程】十七、面向对象语言特性

    【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学 Rust 编程】六、枚举

    2024年02月10日
    浏览(85)
  • 【编程】Rust语言入门第4篇 字符串

    Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但字符串不一样,字符串是 UTF-8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4)。 常见的字符串有两种: str,通常是引用类型, str ,即字符串字面常量,字符串切片。 std::string::String 类型 str 的变

    2024年02月20日
    浏览(54)
  • Go 与 Rust:现代编程语言的深度对比

    在快速发展的软件开发领域中,选择合适的编程语言对项目的成功至关重要。Go 和 Rust 是两种现代编程语言,它们都各自拥有一系列独特的特性和优势。本文旨在深入比较 Go 和 Rust,从不同的角度分析这两种语言,包括性能、语言特性、生态系统、适用场景以及社区支持。

    2024年04月13日
    浏览(46)
  • Rust编程语言入门之cargo、crates.io

    通过 release profile 来自定义构建 在https://crates.io/上发布库 通过 workspaces 组织大工程 从 https://crates.io/来安装库 使用自定义命令扩展 cargo release profile: 是预定义的 可自定义:可使用不同的配置,对代码编译拥有更多的控制 每个 profile 的配置都独立于其它的 profile cargo 主要的

    2023年04月09日
    浏览(53)
  • Rust编程语言入门之函数式语言特性:-迭代器和闭包

    闭包(closures) 迭代器(iterators) 优化改善 12 章的实例项目 讨论闭包和迭代器的运行时性能 闭包:可以捕获其所在环境的匿名函数。 闭包: 是匿名函数 保存为变量、作为参数 可在一个地方创建闭包,然后在另一个上下文中调用闭包来完成运算 可从其定义的作用域捕获值

    2023年04月08日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包