
write marcos by your self
本文最后更新于 2024-03-31,本文发布时间距今超过 90 天, 文章内容可能已经过时。最新内容请以官方内容为准
Write marcos by your self
Some examples
Rust 宏的示例代码,展示了一些常见的用法和技巧:
- 简单的重复代码块:
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 次并打印结果。
- 类似于
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
的值。
- 枚举匹配和展开:
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
的值选择相应的结果并打印输出。
- 获取方法信息:
在 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!
生成代码,使用辅助属性,以及利用 syn
和 proc-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 }"
}