接上文。
宏的分类与实现举例
- 声明宏(Declarative Macro)
- 过程宏 (Procedural Macro)
- 自定义derive宏 (Custom derive)
- 属性宏 (Attribute-like macros)
- 函数宏 (Function-like macros)
声明宏
像vec!
、concat!
这类常用的宏就是用声明宏实现的,可以方便coding,减少代码量。
这里我们举个例子,rust实现里面是不允许出现let a=b=c="1+2";
这类代码的。
我们考虑实现一个宏assign
,可以assign!("1+2", a, b, c)
来达到上述目的。
考虑到刚刚入门,可以从简单的开始实现。
assign!(a, "1+2")
assign!("1+2", a)
assign!("1+2", a, b)
assign!("1+2", a, b, c, ...)
#[macro_export]
macro_rules! assign {
// 实现 assign!("1+2", a) 类型
( $val:expr, $it:ident) => {
let $it = $val;
};
// 实现 assign!("1+2", a, b) 类型
( $val:expr, $it:ident, $it2:ident) => {
let $it = $val;
let $it2 = $val;
};
// 实现 assign!("1+2", a, b, c, d, ...) 任意个参数类型
( $val:expr, $( $it:ident ),*) => {
$(
let $it = $val;
)*
};
}
fn main() {
assign!("1+2", a);
println!("a: {}", a);
assign!(1 + 2, a);
println!("a: {}", a);
assign!(1 + 2, a, b);
println!("a = b = {} = {}", a, b);
assign!(5 + 2, a, b, c);
println!("a = b = c = {} = {} = {}", a, b, c);
}
自定义derive宏
我们在打印的时候,常常会被提示没有实现Debug
trait,建议添加#[derive(Debug)]
。
也就是说,derive宏可以帮我们实现指定的trait。具体如何实现呢?
举个例子,我们将要实现3个trait:
s06_macro
: 任意名称,定义traitHelloMacro
s06_macro_derive
: 前一个crate名称 +_derive
,实现traitHelloMacro
asdssafsfs
: 任意名称,引用traitHelloMacro
并使用derive宏实现它
s06_macro
// src/lib.rs
pub trait HelloMacro {
fn hello_macro();
}
s06_macro_derive
# Cargo.toml
[lib]
proc-macro = true
[dependencies]
# 用于将TokenStream转化为Rust语法树
syn = "1.0"
# 用于将内容转化为TokenStream
quote = "1.0"
se proc_macro::TokenStream;
use quote::quote;
use syn::{self};
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_hello_macro(&ast)
}
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}!", stringify!(#name));
}
}
};
gen.into()
}
示例
# Cargo.toml
[dependencies]
s06_macro = { path = "../s06_macro" }
s06_macro_derive = { path = "../s06_macro_derive" }
// src/main.rs
#[derive(s06_macro_derive::HelloMacro)]
struct Test;
fn main() {
Test::hello_macro();
// 将会打印`Hello, Macro! My name is Test!`
}
属性宏
我看的教程举例用的是路由:
#[route(GET, "/")]
fn index() {
找到了一个最简单的例子,它会把宏的参数
和加宏的函数
在编译的时候全打印出来,加宏的函数
仍然是原来的功能。
如果你要想更复杂的实现,需要参照前面一样,将TokenStream
转为语法树,再来一顿猛如虎的操作,然后转化为TokenStream
返回。
// src/lib.rs
#[proc_macro_attribute]
pub fn show_streams(attr: TokenStream, item: TokenStream) -> TokenStream {
// 下面的打印发生在编译时期
println!("attr: \"{}\"", attr.to_string());
println!("item: \"{}\"", item.to_string());
item
}
// Attribute macros - #[CustomAttribute]
// Example: Basic function
#[show_streams]
fn invoke1() {}
// out: attr: ""
// out: item: "fn invoke1() { }"
// Example: Attribute with input
#[show_streams(bar)]
fn invoke2() {}
// out: attr: "bar"
// out: item: "fn invoke2() {}"
// Example: Multiple tokens in the input
#[show_streams(multiple => tokens)]
fn invoke3() {}
// out: attr: "multiple => tokens"
// out: item: "fn invoke3() {}"
// Example:
#[show_streams { delimiters }]
fn invoke4() {}
// out: attr: "delimiters"
// out: item: "fn invoke4() {}"
// src/main.rs
fn main() {
Test::hello_macro();
invoke1();
invoke2();
invoke3();
invoke4();
}
函数宏
我看的教程举例用的是sql
,它返回的是一个值:
let sql = sql!(SELECT * FROM posts WHERE id=1);
实际上,脑洞还可以放开一点
make_answer!(xxx); // 创建一个answer函数
let answer0 = answer();
print_hello!(); // 打印hello输出
// src/lib.rs
#[proc_macro]
pub fn make_answer(_item: TokenStream) -> TokenStream {
"fn answer() -> u32 { 42 }".parse().unwrap()
}
#[proc_macro]
pub fn print_hello(_item: TokenStream) -> TokenStream {
"println!(\"hello\")".parse().unwrap()
}
常见的宏应用举例
features
- 定义features
# Cargo.toml
[dependencies]
gif = { version = "0.11.1", optional = true }
[features]
default = ["bmp"]
bmp = []
png = []
ico = ["bmp", "png"]
webp = []
gif = ["dep:gif"] # gif feature开启才会引入依赖gif
- 实现features
#[cfg(feature = "png")]
pub mod PNG;
// ...
#[cfg(feature = "webp")]
pub struct Webp{}
- 使用
# Cargo.toml
[dependencies]
s06_macro = {features=["png", "gif"]}
条件编译
features实际上就是运用了条件编译。
具体来说,就是对cfg
宏的使用。
- cfg属性:在属性位置中使用
#[cfg(…)]
- cfg!宏:在布尔表达式中使用
cfg!(…)
// 参考 https://blog.csdn.net/lcloveyou/article/details/105004181
// 仅当目标系统为Linux 的时候才会编译
#[cfg(target_os = "linux")]
fn are_you_on_linux() {
println!("linux!")
}
// 仅当目标系统不是Linux 时才会编译
#[cfg(not(target_os = "linux"))]
fn are_you_on_linux() {
println!("not linux!")
}
fn main() {
are_you_on_linux();
println!("Are you sure?");
if cfg!(target_os = "linux") {
println!("Yes. It's linux!");
} else {
println!("Yes. It's not linux!");
}
}