1

早些时候又被朋友怼了,产出太慢,今天继续接上回说到的自定义宏。

直接开码

话不多说咱直接开码,我们cargo new my_macro创建一个包。然后修改main.rs

extern crate my_macro;

#[macro_use]
extern crate my_macro_derive;

use my_macro::MyMacro;

#[derive(MyMacro)]
struct Coder;

fn main() {
    Corder::my_macro();
}

来来来,我们康康上面的代码,我们看到了#[macro_use]的语法,这是一个注解,可以使被注解的模块重的宏应用到当前的作用域。同样下面的我们又看到一个注解#[derive(MyMacro)],这个则是用来得到一个hello_macro函数的默认实现,也就是会生成对应的代码,然后再main里面尝试调用这个方法。

那么现在我们就要定义MyMacro这个trait(类似接口,我们后面再聊,嘤嘤嘤)了,我们需要新建一个库。可以使用以下命令cargo new my_macro --lib,然后编辑生成的lib.rs文件

pub trait MyMacro {
    fn my_macro();
}

怎么样,看着语法格式是不是就是一个接口;pub是rust中的关键字,表示这个trait是公开其他域可以访问的。
有了接口之后我们就要实现它对应的功能行为,那么我们希望这个接口打印一段话;我们可以这样修改:

extern crate my_macro;

use my_macro::MyMacro;

struct Coder;

impl MyMacro for Coder {
    fn my_macro() {
        println!("热点1:P照片是网红的基本素养");
        println!("热点2:极限挑战");
        println!("热点1:大家还是要坚持戴口罩");
    }
}

fn main() {
    Corder::my_macro();
}

这样我们可以任性的给任何想使用我们的my_macro的类型实现这个代码块啦,这样可以节约一部分工作。但是这样还不行,rust这个小伙子没有反射这个功能,所以他就没办法再运行的时候获取类型名,我们刚刚想偷懒的那部分就不行了,所以我们还需要一个再运行时生产代码的宏。我们习惯性的会在名字后面带上xxx_derive来表明这是一个自定义的过程式宏包。cargo new my_macro_derive --lib

这两个lib息息相关,所以再同一个目录下创建是最好不过的事情,而且必须要同时加入依赖,发布的时候也得一起发布。然后我们需要将 my_macro_derive 声明为一个过程式宏的包。同时也需要 syn 和 quote 包中的功能,也需要将其加到依赖中。为 my_macro_derive 将下面的代码加入到 Cargo.toml 文件中,
注意是my_macro_derive包的配置哟。

[lib]
proc-macro = true

[dependencies]
syn = "*"
quote = "*"

修改完配置我们需要编辑my_macro_derive的lib.rs文件。

extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;

use proc_macro::TokenStream;

#[proc_macro_derive(MyMacro)]
pub fn my_macro_derive(input: TokenStream) -> TokenStream {
    let s = input.to_string();

    let ast = syn::parse_derive_input(&s).unwrap();

    let gen = impl_hello_macro(&ast);

    gen.parse().unwrap()
}

重头戏来了,这段代码可算是有点看头了。刚刚我们新加的包是用来干啥的呢,proc_macro 可以将 Rust 代码转换为相应的字符串。syn 则将字符串中的Rust代码解析成为一个可以操作的数据结构。quote 则将 syn 解析的数据结构反过来传入到 Rust 代码中。知道了这些包的作用之后在看上面的代码一切就变得清晰和明了了。

函数接受一个输入转换为一个字符串类型的变量,然后这个字符串其实就是代表派生MyMacro代码的字符串,然后syn解析转换字符串成一个数据结构。这个结构可以让我们拿到所注解的名字如我们之前定义的Coder名字等信息。

万事具备,只欠一个在注解类型上面实现我们刚刚MyMacro的代码了。继续在 my_macro_derive 中编辑lib.rs

fn impl_my_macro(ast: &syn::DeriveInput) -> quote::Tokens {
    let name = &ast.ident;
    quote! {
        impl MyMacro for #name {
            fn my_macro() {
                println!("你好热点送上了! 类型名: {}.", stringify!(#name));
            }
        }
    }
}

大功告成。在最初的my_macro包中加入对应的依赖:

[dependencies]
hello_macro = { path = "../hello_macro" }
hello_macro_derive = { path = "../hello_macro/hello_macro_derive" }

然后执行文章开头的代码,cargo run。就会打印出:你好热点送上了! 类型名:Corder.

码完跑路(偷懒)

所以接下来就是你们的时间了,好好捋捋上面说的东西吧。(逃


wslongchen
67 声望11 粉丝

码农一枚.