【第四课】rust声明式宏理解与实战
目录
前言
理解宏
实战宏
前言
上一课在介绍vector时,我们再一次提到了rust中的宏,在初始化vector时使用了vec!宏,当时补了一句有机会会好好说明一下rust中的宏,并且写一个hashmap宏来初始化hashmap。想了想一直介绍基本语法还是比较枯燥乏味的,所以这节课我们介绍一点有意思的,我们看看rust中的声明式宏。
理解宏
宏是一种元编程,是一种用代码去生成代码的技术,如果有了解过flink/spark的同学,在flink/spark中有很多算子都是靠代码生成的,事先定义好模版,然后生成对应的代码,最后执行。
rust中的宏也是依赖代码去生成代码。在编译期间会生成对应的代码,第一次学习宏,理解生成代码这一点很重要。
rust中的宏有好几种,我们见到的println! 和 vec!都是声明式宏,本文也主要介绍声明式宏,贪多必失,我们一个一个来。
我们在上一节课使用了vec!宏,我们来看看vec!的定义。
以#开头的也是rust中一种宏,通过字面意思都能看出来个大概,我们来一个一个过一下。
#[cfg(all(not(no_global_oom_handling), not(test)))]
这是条件编译属性。牢记宏是在生成代码,这里的意思就是在哪些条件下编译生成代码,no_global_oom_handling表示禁用全局oom处理,test表示测试环境,前面有not,表示在没有禁用全局oom处理和测试的条件下,这个宏才是可以被编译的,就是说在测试环境和禁用全局oom处理的情况下代码无法被编译。
#[macro_export]
表示这个宏可以被导出,可以被导出的意思就是,可以被别的模块导入使用
#[stable(feature = "rust1", since = "1.0.0")]
这个就更明显了,从rust1.0开始的稳定feature
#[rustc_diagnostic_item = "vec_macro"]
给这个宏取了一个名字,在诊断的时候有用
#[allow_internal_unstable(rustc_attrs, liballoc_internals)]
允许使用一些内部不稳定的东西,我也没太看懂,但感觉也不必过于纠结
接下来看宏的定义部分
macro_rules! vec
这一行定义了一个叫vec的宏,后面{}中包含的是这个宏的具体内容,我们一个一个部分
() => (
$crate::vec::Vec::new()
);
这个匹配的是vec![]的情况,简单明了,如果调用的宏没有提供参数的话,生成的是Vec::new()函数
($elem:expr; $n:expr) => (
$crate::vec::from_elem($elem, $n)
);
这个匹配的是vec![1;3]的情况,生成的代码是std::vec::from_elem(1, 3);看起来也比较简单
($($x:expr),+ $(,)?) => (
<[_]>::into_vec(
// This rustc_box is not required, but it produces a dramatic improvement in compile
// time when constructing arrays with many elements.
#[rustc_box]
$crate::boxed::Box::new([$($x),+])
)
);
这个匹配的是vec![1,2,3,4,5]的情况,看起来是最复杂的一个了,我们来拆解一下看一看,很像正则对不对,
$($x:expr),+ 是第一部分,+表示有一个或多个$($x:expr), 注意逗号也是一部分哦,expr是表达式,并且取了一个名字x
$(,)?是第二部分,?表示前面的部分是可选的,意思就是也许会有一个逗号在末尾
#[cfg(all(not(no_global_oom_handling), not(test)))]
#[macro_export]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "vec_macro"]
#[allow_internal_unstable(rustc_attrs, liballoc_internals)]
macro_rules! vec {
() => (
$crate::vec::Vec::new()
);
($elem:expr; $n:expr) => (
$crate::vec::from_elem($elem, $n)
);
($($x:expr),+ $(,)?) => (
<[_]>::into_vec(
// This rustc_box is not required, but it produces a dramatic improvement in compile
// time when constructing arrays with many elements.
#[rustc_box]
$crate::boxed::Box::new([$($x),+])
)
);
}
实战宏
我们来仿造vec宏,来编写一个hashmap的宏吧
我们的目的是想完成一个hashmap宏,接收二元组作为map的key和value,下面代码仿造vec宏的写法,我们来一一解读一下。
() => {},这是默认的格式
$(($key:expr, $value:expr)),* 这表示($key:expr, $value:expr)这个二元组会重复多次,使用逗号分隔。
我们在定义中先定义了一个HashMap,然后重复执行insert操作,最后返回hashmap
当我们在编译hashmap!宏时let my_map = hashmap!(("a",1))
实际上,代码会被替换为let my_map = {
let mut t = Hashmap::new();
t.insert("a",1);
t
}
讲到这里,其实可以很好理解为什么声明宏是模版代码了,我们定义了模版,在编译期间,将模版代码替换执行的代码。
use std::collections::HashMap;
macro_rules! hashmap {
($(($key:expr, $value:expr)),*) => {
{
let mut t = HashMap::new();
$(
t.insert($key, $value);
)*
t
}
}
}
fn main() {
let v1 = vec![1; 3];
let v2 = std::vec::from_elem(1, 3);
let my_map = hashmap!(("a",1));
println!("my_map['a']={}", my_map["a"]);
}
总结
这节课承接是上节课在介绍vector和hashmap时,穿插进来的支线任务,下节会回归到主线,继续介绍rust中的基本语法,了解一下rust中的流程控制。这节课由vec宏引起,介绍了rust中的声明式宏,类似函数,但是比函数更加灵活和强大,通过模版代码的方式完成功能。因为是在编译期间就完成了,所以并不会有运行时的性能影响,关于宏的其他内容,我们在后面再慢慢提起。