本文最后更新于 2024-03-31,本文发布时间距今超过 90 天, 文章内容可能已经过时。最新内容请以官方内容为准

Write marcos by your self

Some examples

Rust 宏的示例代码,展示了一些常见的用法和技巧:

  1. 简单的重复代码块:
macro_rules! repeat {
    ($expr:expr; $n:expr) => {
        {
            let mut result = Vec::new();
            for _ in 0..$n {
                result.push($expr);
            }
            result
        }
    };
}

fn main() {
    let repeated = repeat!(42; 5);
    println!("{:?}", repeated);
}

这个示例定义了一个名为 repeat! 的宏,它接受一个表达式和一个重复次数,并返回一个包含重复表达式的向量。在 main 函数中,我们使用 repeat! 宏重复了数字 42 5 次并打印结果。

  1. 类似于 println! 的调试宏:
macro_rules! debug {
    ($($arg:tt)*) => {
        {
            #[cfg(debug_assertions)]
            {
                println!($($arg)*);
            }
        }
    };
}

fn main() {
    let x = 42;
    debug!("The value of x is {}", x);
}

这个示例定义了一个名为 debug! 的宏,它类似于 println!,但只在开启了调试断言时才会打印输出。在 main 函数中,我们使用 debug! 宏打印了变量 x 的值。

  1. 枚举匹配和展开:
macro_rules! match_enum {
    ($value:expr, $($pattern:pat => $result:expr),*) => {
        match $value {
            $($pattern => $result),*,
            _ => panic!("Unexpected value"),
        }
    };
}

fn main() {
    let value = Some(42);
    let result = match_enum!(value, Some(x) => x * 2, None => 0);
    println!("Result: {}", result);
}

这个示例定义了一个名为 match_enum! 的宏,它接受一个枚举值和一系列模式/结果对,并根据匹配选择对应的结果。在 main 函数中,我们使用 match_enum! 宏根据 value 的值选择相应的结果并打印输出。

  1. 获取方法信息:

在 Rust 中,你可以使用 std::panic::Location 类型来获取函数名和源代码位置。

你可以通过在函数中调用 Location::caller() 方法来获取当前调用者的信息。

示例代码如下:

use std::panic::Location;

fn main() {
    my_function();
}

fn my_function() {
    let location = Location::caller();
    // println!("Function name: {}", location.function()); //名称
    println!("File: {}", location.file());              //文件名
    println!("Line: {}", location.line());              //行号
    println!("Column: {}", location.column());          //列号
}

如果想要获取方法的名称,可以使用 std::any::type_name::<T>() 方法。


fn get_fn_name<F>(_: F) -> &'static str {
    std::any::type_name::<F>()
}

fn println_fn_name<F>(_: F) {
    println!("start of {}", std::any::type_name::<F>());
}

需要注意的是,获取函数名和源代码位置是基于运行时的反射机制,因此会有一定的性能开销。

在生产环境中,应谨慎使用,并在必要时进行优化或禁用。


这些示例只是 Rust 宏的一小部分,但它们展示了一些常见的用法和技巧。

Rust 的宏系统非常强大,可以用于编写更复杂的代码生成和元编程任务。

Rust 宏入门指南

起因

在 Rust 编程中,宏是一种强大的元编程机制,它允许开发者扩展语言的语法和自动执行重复的任务。

宏可以显著提高代码的可读性和可维护性,同时减少手动编码的工作量。Rust 提供了两种主要的宏类型:声明式宏(declarative macros)过程式宏(procedural macros

方法定义

Rust 中最常用的宏形式是声明式宏。声明式宏也被称为“宏示例”、“macro_rules! 宏”或简称为“宏”。在宏的核心,声明式宏允许你编写类似于 Rust match 表达式的代码。宏将传递给宏的 Rust 源代码与模式进行比较,然后用与模式匹配的代码替换传递给宏的代码。这一切都发生在编译时。

要定义一个宏,你需要使用 macro_rules! 宏。让我们通过查看 vec! 宏的定义来了解如何使用 macro_rules!

#[macro_export]
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

在这个例子中,我们定义了一个名为 vec! 的宏。宏定义以 macro_rules! 开始,后面跟着宏的名称(在这里是 vec),然后是用花括号括起来的宏体。

宏体的结构类似于 match 表达式。在这个宏中,我们只有一个模式 ( $( $x:expr ),* ),后面跟着 => 和与该模式关联的代码块。如果模式匹配成功,关联的代码块将被展开。

在宏定义中,模式的语法与第 18 章中讨论的模式语法不同,因为宏模式是针对 Rust 代码结构而不是值进行匹配的。在这个例子中,模式的各个部分的含义如下:

  • 使用一对括号将整个模式括起来。
  • 使用美元符号($)在宏系统中声明一个变量,该变量将包含与模式匹配的 Rust 代码。美元符号使得它是一个宏变量,而不是普通的 Rust 变量。
  • 接下来是一对括号,用于捕获与括号内的模式匹配的值,以便在替换代码中使用。在 $() 中,$x:expr 匹配任何 Rust 表达式,并将该表达式命名为 $x
  • 逗号表示在匹配的代码后面可以选择性地出现逗号分隔符。
  • 星号(*)指定模式可以匹配零个或

在 Rust 中定义宏,使用 macro_rules! 关键字。以下是一个简单的声明式宏的例子:

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

fn main() {
    say_hello!(world); // 输出 "Hello, world!"
}

有参数的宏

有参数的宏允许你传入参数,以便在宏内部动态生成代码。上面的 say_hello 宏就是一个有参数的宏。

无参数的宏

无参数的宏(也称为零参数宏)不接受任何参数。这种宏适用于生成重复的代码段或定义常量。

macro_rules! define_pi {
    () => {
        const PI: f64 = 3.14159;
    };
}

fn main() {
    define_pi!(); // 定义常量 PI
    println!("PI is: {}", PI);
}

如何基本构建宏

构建宏需要使用 macro_rules! 定义宏的模式(pattern)和相应的生成代码(code)。模式可以匹配不同的语法元素,如表达式、语句或代码块。

如何基本使用宏

在 Rust 代码中使用宏时,只需通过宏名和参数(如果有的话)调用宏。宏将在编译时展开,基于提供的参数生成期望的代码。

如何完善宏

为了提高宏的可用性和可读性,可以使用多种技术,包括使用 quote! 生成代码,使用辅助属性,以及利用 synproc-macro2 等外部库来操作抽象语法树(AST)。

如何进阶宏

掌握宏的基础知识后,可以探索高级宏技术,如属性宏(attribute macros)、派生宏(derive macros)和过程式宏(procedural macros)。属性宏允许你定义可以应用于函数、结构体或其他项的自定义属性。派生宏自动化了为自定义类型实现常见特性的过程。过程式宏提供了更多的能力,允许你在编译时操作代码。

示例代码

以下是一个过程式宏的简单示例,它将结构体转换为可打印的格式:

#[macro_use]
extern crate derive;

use derive::Derive;

#[derive(Show)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 10, y: 20 };
    println!("{}", p); // 输出 "Point { x: 10, y: 20 }"
}

引用