199 lines
4.4 KiB
Markdown
199 lines
4.4 KiB
Markdown
# Rust 宏编程(1)
|
||
|
||
|
||
|
||
rust 宏主要分为 派生宏、类函数宏、属性宏,可以非常方便的生成重复性代码。
|
||
|
||
终于可以能写会写代码的代码了罒ω罒。
|
||
|
||
### 环境准备
|
||
|
||
```bash
|
||
# 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
|
||
|
||
- 目录结构
|
||
|
||
```bash
|
||
|
||
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文件
|
||
```
|
||
|
||
- 编写宏代码
|
||
|
||
```rust
|
||
use proc_macro::TokenStream;
|
||
|
||
#[proc_macro_derive(Builder)]
|
||
pub fn derive(input: TokenStream) -> TokenStream {
|
||
let _ = input;
|
||
// 打印看看input到底是什么
|
||
eprintln!("{:#?}", input);
|
||
// 注释下行 并返回空的结果, 下行表示未实现该发放并进行panic
|
||
// unimplemented!()
|
||
TokenStream::new()
|
||
}
|
||
```
|
||
|
||
- 运行 test
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```rust
|
||
use proc_macro::TokenStream;
|
||
|
||
#[proc_macro_derive(Builder)]
|
||
pub fn derive(input: TokenStream) -> TokenStream {
|
||
eprintln!("{:#?}", input);
|
||
// 这里直接把传入的结构返回回去
|
||
input
|
||
}
|
||
|
||
```
|
||
|
||
- main.rs 根目录下
|
||
|
||
```rust
|
||
use derive_builder::Builder;
|
||
|
||
#[derive(Builder)]
|
||
pub struct Command {
|
||
executable: String,
|
||
args: Vec<String>,
|
||
env: Vec<String>,
|
||
current_dir: String,
|
||
}
|
||
|
||
fn main() {}
|
||
```
|
||
|
||
- 查看扩展代码
|
||
|
||
```bash
|
||
# 进入根目录, 运行
|
||
cargo expand --bin workshop
|
||
# 等同于
|
||
cargo expand
|
||
# 输出
|
||
```
|
||
|
||
```rust
|
||
#![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结构体
|
||
|
||
```bash
|
||
# 运行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已经看了一周多了,还没开始写实际代码。 |