Rust macro

任何C或Fortran程序复杂到一定程度之后,都会包含一个临时开发的、只有一半功能的、不完全符合规格的、到处都是bug的、运行速度很慢的Common Lisp实现。 -- 《黑客与画家》

Rust中的宏就两大类:对代码模板做简单替换的声明宏(declarative macro)、可以深度定制和生成代码的过程宏(procedural macro)

声明宏

vec![]println!、以及 info!,它们都是声明宏。

常用的 tracing log 的宏定义(代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
macro_rules! __tracing_log {
(target: $target:expr, $level:expr, $($field:tt)+ ) => {
$crate::if_log_enabled! { $level, {
use $crate::log;
let level = $crate::level_to_log!($level);
if level <= log::max_level() {
let log_meta = log::Metadata::builder()
.level(level)
.target($target)
.build();
let logger = log::logger();
if logger.enabled(&log_meta) {
logger.log(&log::Record::builder()
.file(Some(file!()))
.module_path(Some(module_path!()))
.line(Some(line!()))
.metadata(log_meta)
.args($crate::__mk_format_args!($($field)+))
.build());
}
}
}}
};
}

可以看到,它主要做的就是通过简单的接口,把不断重复的逻辑包装起来,然后在调用的地方展开而已,不涉及语法树的操作。

Rust 的声明宏相比C/C++更加安全,你无法在需要出现标识符的地方出现表达式,也无法让宏内部定义的变量污染宏外部的内容。 如果重复性的代码无法用函数来封装,那么声明宏就是一个好的选择。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#[macro_export]
macro_rules! my_vec {
// 没带任何参数的 my_vec,我们创建一个空的 vec
() => {
std::vec::Vec::new()
};
// 处理 my_vec![1, 2, 3, 4]
// $el:expr 是说把匹配到的表达式命名为 $el。$(...)
// * 告诉编译器可以匹配任意多个以逗号分隔的表达式,然后捕获到的每一个表达式可以用 $el 来访问
($($el:expr),*) => ({
let mut v = std::vec::Vec::new();
// $(v.push($el);)* 相当于匹配出多少个 $el就展开多少句 push 语句
$(v.push($el);)*
v
});
// 处理 my_vec![0; 10]
($el:expr; $n:expr) => {
std::vec::from_elem($el, $n)
}
}

使用声明宏时,需要为参数明确类型,可用类型有:

  • item,比如一个函数、结构体、模块等。
  • block,代码块。比如一系列由花括号包裹的表达式和语句。
  • stmt,语句。比如一个赋值语句。
  • pat,模式。
  • expr,表达式。刚才的例子使用过了。
  • ty,类型。比如 Vec。
  • ident,标识符。比如一个变量名。
  • path,路径。比如:foo::std::mem::replacetransmute::<_, int>
  • meta,元数据。一般是在 #[...]#![...] 属性内部的数据。
  • tt,单个的 token 树。
  • vis,可能为空的一个 Visibility 修饰符。比如 pub、pub(crate)

过程宏

过程宏有三种:

  • 函数宏(function-like macro):看起来像函数的宏,但在编译期进行处理。

  • 属性宏(attribute macro):可以在其他代码块上添加属性,为代码块提供更多功能。

  • 派生宏(derive macro):为 derive 属性添加新的功能。

proc-macro-workshop: 过程宏参考

宏会为别人理解你的代码,使用你的代码带来额外的负担。由于宏会生成代码,大量使用宏会让你的代码在不知不觉中膨胀,也会导致二进制很大。当一个功能可以用函数表达时,不要用宏。不要过分迷信于编译时的处理,不要把它当成提高性能的手段。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!