一、宏
看到宏,学过C/C++的小牛们都会嗨起来,这玩意儿不好使啊。基本上在c++编程中,都不推荐使用宏来进行编程,为啥?这玩意儿出了错不好调试不好定位。强大归强大,一个不留神就把自己给掉打一顿,真心没啥人用。不过,看一些有名的框架其实都在大量使用宏。用宏编程有优势,一个是可重复高效使用,第二个是可以动态生成代码。特别是后者,对于C/C++这种不支持反射的语言,简单就是一种额外的Bug啊。看过MFC源码的人估计都明白用宏搞类的自动生成有多么强大,当然,也有多么的难懂。
其实在Rust中,就是复用了这两个特点。还起了个名字,叫语法级操作,把它和函数区别开来。好吧,新程序员们闪开,让Rust闪亮登场。
二、宏和的分类
按照Rust中的具体的用法,宏主要分为两类:
1、使用 macro_rules! 的 声明(Declarative)宏
2、过程宏,它们又为分三种:
第一种,自定义 #[derive] 宏,类似于其它语言中的语法识别,可以通过 derive 属性增加代码应用在结构体和枚举上。
第二种,类属性(Attribute-like)宏,这个就有点类似于其它语言的属性设置注解了。
第三种,类函数宏,类似于一种传递参数的函数的Token。
三、宏和函数
上面提到过,宏可以实现语法生成代码,所以基本上就是一种元编程的方法。在C/C++中,可以通过宏但是更多的是通过模板实现元编程。元编程的优点很多,但同样,难于理解和调试同样如影随形。另外,宏编程还可以接受类似于C/C++的变参,这也是普通函数所无法比拟的。
宏需要提前定义,然后才能使用;但函数可以随意定义和使用。
宏更多的是在框架或者库中定义使用。
常见的宏有print!,vec!等等。
四、宏的应用
宏的应用方法是加一个感叹号,如上面提到的print!,vec!一样。这个是非常明显的和其它语言或者同函数的不同之处。下面就按照宏的分类看一下具体的例子:
1、声明宏
macro_rules! create_function {
($func_name:ident) => (
fn $func_name() {
println!("function {:?} is called", stringify!($func_name))
}
)
}
fn main() {
create_function!(foo);
foo();
}
2、过程宏
自定义宏,就是用derive来搞定,这个关键字只能用于结构体和枚举体:
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;
#[derive(HelloMacro)]
struct Pancakes;
fn main() {
Pancakes::hello_macro();
}
类属性宏,它比derive更灵活,可以自定义不同的属性,特别是应用在函数中,有JAVA编程经验会发现这玩意儿有点类似于注解:
#[route(GET, "/")]
fn index() {}
函数宏其实是更像C/C++中的宏的普通应用,就是生成一个函数的宏:
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {}
Rust过程宏只能访问宏定义及所在的库和其依赖项,不能够控制编译过程,或者理解成作用域是受限的。过程宏的使用在Rust社区提供了三个库,即Sync、quote、proc_macro2.
- 代码主要来自Rust社区
五、总结
“没有买卖就没有伤害”!同样,在语言中,如果没有优势,没有人使用,即使设计出一个功能来,最终也会被废弃。宏即使有千般不好,但自然有它的优势所在,而这个优势,在没有其它形式替代它前,仍然是会在应用的场景中大放光芒的。骂,骂不死的,还得努力干,上好技术,替代它。桃李无言,下自成蹊!少说,少骂,多想,多干!
努力吧,归来的少年!