mynote/rust/marco.md
2022-06-05 10:19:11 +08:00

4.4 KiB
Raw Blame History

Rust 宏编程1

rust 宏主要分为 派生宏、类函数宏、属性宏,可以非常方便的生成重复性代码。

终于可以能写会写代码的代码了罒ω罒。

环境准备

# dtolnay 大佬提供的 宏教学库
git clone https://github.com/dtolnay/proc-macro-workshop.git

## cargo expand  查看宏产生的全部代码
rustup toolchain install nightly-x86_64-apple-darwin
# 可先执行该指令 会提示你看着那个版本的nightly
cargo +nightly install cargo-expand

cargo test

  • 目录结构

cd builder/
tree

.
├── Cargo.toml
├── src
│   └── lib.rs    # 要编写宏的文件
└── tests
    ├── 01-parse.rs
    ├── 02-create-builder.rs
    ├── 03-call-setters.rs
    ├── 04-call-build.rs
    ├── 05-method-chaining.rs
    ├── 06-optional-field.rs
    ├── 07-repeated-field.rs
    ├── 08-unrecognized-attribute.rs
    ├── 08-unrecognized-attribute.stderr
    ├── 09-redefined-prelude-types.rs
    └── progress.rs  # 通过取消注释来确定运行那个test文件
  • 编写宏代码
use proc_macro::TokenStream;

#[proc_macro_derive(Builder)]
pub fn derive(input: TokenStream) -> TokenStream {
    let _ = input;
  	// 打印看看input到底是什么
    eprintln!("{:#?}", input);
  	// 注释下行 并返回空的结果, 下行表示未实现该发放并进行panic
    // unimplemented!()
    TokenStream::new()
}
  • 运行 test
# builder 目录下
cargo test
# 可以查看 test 运行成功无报错 并打印input 结构
TokenStream [
    Ident {
        ident: "pub",
        span: #0 bytes(990..993),
    },
    Ident {
        ident: "struct",
        span: #0 bytes(994..1000),
    },
    Ident {
        ident: "Command",
        span: #0 bytes(1001..1008),
    },
    Group {
        delimiter: Brace,
        stream: TokenStream [
            Ident {
                ident: "executable",
                span: #0 bytes(1015..1025),
            },
          	....
          	....
            ...
            Punct {
                ch: ',',
                spacing: Alone,
                span: #0 bytes(1103..1104),
            },
        ],
        span: #0 bytes(1009..1106),
    },
]

cargo expand

  • builder/src/lib.rs

    use proc_macro::TokenStream;
    
    #[proc_macro_derive(Builder)] 
    pub fn derive(input: TokenStream) -> TokenStream {
        eprintln!("{:#?}", input);
        // 这里直接把传入的结构返回回去
      	input
    }
    
    
  • main.rs 根目录下

    use derive_builder::Builder;
    
    #[derive(Builder)]
    pub struct Command {
        executable: String,
        args: Vec<String>,
        env: Vec<String>,
        current_dir: String,
    }
    
    fn main() {}
    
  • 查看扩展代码

    # 进入根目录, 运行
    cargo expand --bin workshop
    # 等同于
    cargo expand
    # 输出
    
    #![feature(prelude_import)]
    #[prelude_import]
    use std::prelude::rust_2021::*;
    #[macro_use]
    extern crate std;
    use derive_builder::Builder;
    pub struct Command {
        executable: String,
        args: Vec<String>,
        env: Vec<String>,
        current_dir: String,
    }
    pub struct Command {
        executable: String,
        args: Vec<String>,
        env: Vec<String>,
        current_dir: String,
    }
    fn main() {}
    
    

    可以看到生成了一个 多的Command结构体

    # 运行cargo test 可以看到报错 重复定义的Command
    
    ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
    error[E0428]: the name `Command` is defined multiple times
      --> tests/01-parse.rs:27:1
       |
    27 | pub struct Command {
       | ^^^^^^^^^^^^^^^^^^
       | |
       | `Command` redefined here
       | previous definition of the type `Command` here
       |
       = note: `Command` must be defined only once in the type namespace of this module
    ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
    
    

剩下的就是根据tests/目录下每个测试文件编写符合要求的宏代码。

祝各位顺利~

rust真TM难学其他语言能看着demo直接上手写项目 rus已经看了一周多了还没开始写实际代码。