NiceLeeのBlog 用爱发电 bilibili~

Rust 入门时容易迷惑的技术细节(macro篇)

2022-12-01
nIceLee

阅读:


接上文。

宏的分类与实现举例

  • 声明宏(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!");
	}
}

内容
隐藏