Skip to content

rust宏 #11

@wuyuedefeng

Description

@wuyuedefeng

属性式过程宏

参考资料: https://blog.ideawand.com/2021/02/27/rust_procedural_macro/rust_proc_marco_workshop_guide-01/

初始化项目

cargo new my_demo --bin
cd my_demo
cargo new proc_macro_first --lib
  • 编辑my_demo/proc_macro_first/Cargo.toml, 添加:
[lib]
proc-macro = true
  • 编辑my_demo/proc_macro_first/src/lib.rs, 添加:
use proc_macro::TokenStream;

// 告诉编译器定义的是 属性式 的过程宏; 函数式过程宏使用:#[proc_macro], 派生式过程宏使用:#[proc_macro_derive]
#[proc_macro_attribute] 
// attr 为参数,  item为标记对象
pub fn my_test_proc_macro_attribute(attr: TokenStream, item: TokenStream) -> TokenStream {
    eprintln!("attr -> {:#?}", attr);
    eprintln!("item -> {:#?}", item);
    item
}
  • 编辑my_demo/Cargo.toml, 添加:
[dependencies]
proc_macro_first = { path = "./proc_macro_first"}
  • 编辑my_demo/src/main.rs, 添加:
use proc_macro_first::my_test_proc_macro_attribute;

#[my_test_proc_macro_attribute(blog(::hello::world))]
pub fn foo(i: i32) {
    println!("Hello foo! {}", i);
}
fn main() {
    println!("Hello, world!");
}
执行`cargo check`
 
attr -> TokenStream [
    Ident {
        ident: "blog",
        span: #0 bytes(84..88),
    },
    Group {
        delimiter: Parenthesis,
        stream: TokenStream [
            Punct {
                ch: ':',
                spacing: Joint,
                span: #0 bytes(89..91),
            },
            Punct {
                ch: ':',
                spacing: Alone,
                span: #0 bytes(89..91),
            },
            Ident {
                ident: "hello",
                span: #0 bytes(91..96),
            },
            Punct {
                ch: ':',
                spacing: Joint,
                span: #0 bytes(96..98),
            },
            Punct {
                ch: ':',
                spacing: Alone,
                span: #0 bytes(96..98),
            },
            Ident {
                ident: "world",
                span: #0 bytes(98..103),
            },
        ],
        span: #0 bytes(88..104),
    },
]
item -> TokenStream [
    Ident {
        ident: "pub",
        span: #0 bytes(107..110),
    },
    Ident {
        ident: "fn",
        span: #0 bytes(111..113),
    },
    Ident {
        ident: "foo",
        span: #0 bytes(114..117),
    },
    Group {
        delimiter: Parenthesis,
        stream: TokenStream [
            Ident {
                ident: "i",
                span: #0 bytes(118..119),
            },
            Punct {
                ch: ':',
                spacing: Alone,
                span: #0 bytes(119..120),
            },
            Ident {
                ident: "i32",
                span: #0 bytes(121..124),
            },
        ],
        span: #0 bytes(117..125),
    },
    Group {
        delimiter: Brace,
        stream: TokenStream [
            Ident {
                ident: "println",
                span: #0 bytes(132..139),
            },
            Punct {
                ch: '!',
                spacing: Alone,
                span: #0 bytes(139..140),
            },
            Group {
                delimiter: Parenthesis,
                stream: TokenStream [
                    Literal {
                        kind: Str,
                        symbol: "Hello foo! {}",
                        suffix: None,
                        span: #0 bytes(141..156),
                    },
                    Punct {
                        ch: ',',
                        spacing: Alone,
                        span: #0 bytes(156..157),
                    },
                    Ident {
                        ident: "i",
                        span: #0 bytes(158..159),
                    },
                ],
                span: #0 bytes(140..160),
            },
            Punct {
                ch: ';',
                spacing: Alone,
                span: #0 bytes(160..161),
            },
        ],
        span: #0 bytes(126..163),
    },
]

* 使用synTokenStream转换成更高级的TokenStream, quoto!可以将高级的TokenStream转换回函数支持的返回值类型的TokenStream进行返回

  • 编辑my_demo/proc_macro_first/Cargo.toml, 添加:
[dependencies]
quote = "1"
syn = { version = "*", features = ["full", "extra-traits"] }
  • 编辑my_demo/proc_macro_first/src/lib.rs, 添加:
use proc_macro::TokenStream;
use syn::{parse_macro_input, AttributeArgs, Item};
use quote::quote;

// 告诉编译器定义的是 属性式 的过程宏; 函数式过程宏使用:#[proc_macro], 派生式过程宏使用:#[proc_macro_derive]
#[proc_macro_attribute]
// attr 为参数
pub fn my_test_proc_macro_attribute(attr: TokenStream, item: TokenStream) -> TokenStream {
    eprintln!("attr -> {:#?}", parse_macro_input!(attr as AttributeArgs));
    let body_ast = parse_macro_input!(item as Item);
    eprintln!("item -> {:#?}", body_ast);
    // quote!返回的是quote内置依赖的proc_macro2::TokenStream,调用into方法进一步转换为系统的proc_macro::TokenStream
    quote!(#body_ast).into()
}
执行`cargo check`
 
attr -> [
    Meta(
        List(
            MetaList {
                path: Path {
                    leading_colon: None,
                    segments: [
                        PathSegment {
                            ident: Ident {
                                ident: "blog",
                                span: #0 bytes(84..88),
                            },
                            arguments: None,
                        },
                    ],
                },
                paren_token: Paren,
                nested: [
                    Meta(
                        Path(
                            Path {
                                leading_colon: Some(
                                    Colon2,
                                ),
                                segments: [
                                    PathSegment {
                                        ident: Ident {
                                            ident: "hello",
                                            span: #0 bytes(91..96),
                                        },
                                        arguments: None,
                                    },
                                    Colon2,
                                    PathSegment {
                                        ident: Ident {
                                            ident: "world",
                                            span: #0 bytes(98..103),
                                        },
                                        arguments: None,
                                    },
                                ],
                            },
                        ),
                    ),
                ],
            },
        ),
    ),
]
item -> Fn(
    ItemFn {
        attrs: [],
        vis: Public(
            VisPublic {
                pub_token: Pub,
            },
        ),
        sig: Signature {
            constness: None,
            asyncness: None,
            unsafety: None,
            abi: None,
            fn_token: Fn,
            ident: Ident {
                ident: "foo",
                span: #0 bytes(114..117),
            },
            generics: Generics {
                lt_token: None,
                params: [],
                gt_token: None,
                where_clause: None,
            },
            paren_token: Paren,
            inputs: [
                Typed(
                    PatType {
                        attrs: [],
                        pat: Ident(
                            PatIdent {
                                attrs: [],
                                by_ref: None,
                                mutability: None,
                                ident: Ident {
                                    ident: "i",
                                    span: #0 bytes(118..119),
                                },
                                subpat: None,
                            },
                        ),
                        colon_token: Colon,
                        ty: Path(
                            TypePath {
                                qself: None,
                                path: Path {
                                    leading_colon: None,
                                    segments: [
                                        PathSegment {
                                            ident: Ident {
                                                ident: "i32",
                                                span: #0 bytes(121..124),
                                            },
                                            arguments: None,
                                        },
                                    ],
                                },
                            },
                        ),
                    },
                ),
            ],
            variadic: None,
            output: Default,
        },
        block: Block {
            brace_token: Brace,
            stmts: [
                Semi(
                    Macro(
                        ExprMacro {
                            attrs: [],
                            mac: Macro {
                                path: Path {
                                    leading_colon: None,
                                    segments: [
                                        PathSegment {
                                            ident: Ident {
                                                ident: "println",
                                                span: #0 bytes(132..139),
                                            },
                                            arguments: None,
                                        },
                                    ],
                                },
                                bang_token: Bang,
                                delimiter: Paren(
                                    Paren,
                                ),
                                tokens: TokenStream [
                                    Literal {
                                        kind: Str,
                                        symbol: "Hello foo! {}",
                                        suffix: None,
                                        span: #0 bytes(141..156),
                                    },
                                    Punct {
                                        ch: ',',
                                        spacing: Alone,
                                        span: #0 bytes(156..157),
                                    },
                                    Ident {
                                        ident: "i",
                                        span: #0 bytes(158..159),
                                    },
                                ],
                            },
                        },
                    ),
                    Semi,
                ),
            ],
        },
    },
)

派生式过程宏derive

可以把派生式过程宏认为是一种特殊的属性式过程宏,把属性式过程宏看成是派生式过程宏的拓展版本,能用派生式过程宏实现的,用属性式过程宏也可以实现。**区别: 派生式过程宏不能修改用户的代码,只能在修饰的代码后面追加新代码

参考资料

* 第一关

  • 编辑my_demo/proc_macro_first/src/lib.rs, 写入:
use proc_macro::TokenStream;
use syn;

#[proc_macro_derive(Builder)] //这里的Builder及derive宏的名称
pub fn derive_builder(input: TokenStream) -> TokenStream { // 合理函数名称无所谓
    let st = syn::parse_macro_input!(input as syn::DeriveInput);
    eprintln!("{:#?}", st);
    // 和属性式过程宏不同,这里返回空的TokenStream代表不对用户代码追加任何代码(不修改用户代码)
    TokenStream::new()
}
  • 编辑my_demo/src/main.rs, 写入:
use proc_macro_first::Builder;

#[derive(Builder)]
pub struct Command {
    executable: String,
    args: Vec<String>,
    env: Vec<String>,
    current_dir: String,
}

fn main() {
    println!("Hello, world!");
}
执行`cargo check`
 
DeriveInput {
    attrs: [],
    vis: Public(
        VisPublic {
            pub_token: Pub,
        },
    ),
    ident: Ident {
        ident: "Command",
        span: #0 bytes(62..69),
    },
    generics: Generics {
        lt_token: None,
        params: [],
        gt_token: None,
        where_clause: None,
    },
    data: Struct(
        DataStruct {
            struct_token: Struct,
            fields: Named(
                FieldsNamed {
                    brace_token: Brace,
                    named: [
                        Field {
                            attrs: [],
                            vis: Inherited,
                            ident: Some(
                                Ident {
                                    ident: "executable",
                                    span: #0 bytes(76..86),
                                },
                            ),
                            colon_token: Some(
                                Colon,
                            ),
                            ty: Path(
                                TypePath {
                                    qself: None,
                                    path: Path {
                                        leading_colon: None,
                                        segments: [
                                            PathSegment {
                                                ident: Ident {
                                                    ident: "String",
                                                    span: #0 bytes(88..94),
                                                },
                                                arguments: None,
                                            },
                                        ],
                                    },
                                },
                            ),
                        },
                        Comma,
                        Field {
                            attrs: [],
                            vis: Inherited,
                            ident: Some(
                                Ident {
                                    ident: "args",
                                    span: #0 bytes(100..104),
                                },
                            ),
                            colon_token: Some(
                                Colon,
                            ),
                            ty: Path(
                                TypePath {
                                    qself: None,
                                    path: Path {
                                        leading_colon: None,
                                        segments: [
                                            PathSegment {
                                                ident: Ident {
                                                    ident: "Vec",
                                                    span: #0 bytes(106..109),
                                                },
                                                arguments: AngleBracketed(
                                                    AngleBracketedGenericArguments {
                                                        colon2_token: None,
                                                        lt_token: Lt,
                                                        args: [
                                                            Type(
                                                                Path(
                                                                    TypePath {
                                                                        qself: None,
                                                                        path: Path {
                                                                            leading_colon: None,
                                                                            segments: [
                                                                                PathSegment {
                                                                                    ident: Ident {
                                                                                        ident: "String",
                                                                                        span: #0 bytes(110..116),
                                                                                    },
                                                                                    arguments: None,
                                                                                },
                                                                            ],
                                                                        },
                                                                    },
                                                                ),
                                                            ),
                                                        ],
                                                        gt_token: Gt,
                                                    },
                                                ),
                                            },
                                        ],
                                    },
                                },
                            ),
                        },
                        Comma,
                        Field {
                            attrs: [],
                            vis: Inherited,
                            ident: Some(
                                Ident {
                                    ident: "env",
                                    span: #0 bytes(123..126),
                                },
                            ),
                            colon_token: Some(
                                Colon,
                            ),
                            ty: Path(
                                TypePath {
                                    qself: None,
                                    path: Path {
                                        leading_colon: None,
                                        segments: [
                                            PathSegment {
                                                ident: Ident {
                                                    ident: "Vec",
                                                    span: #0 bytes(128..131),
                                                },
                                                arguments: AngleBracketed(
                                                    AngleBracketedGenericArguments {
                                                        colon2_token: None,
                                                        lt_token: Lt,
                                                        args: [
                                                            Type(
                                                                Path(
                                                                    TypePath {
                                                                        qself: None,
                                                                        path: Path {
                                                                            leading_colon: None,
                                                                            segments: [
                                                                                PathSegment {
                                                                                    ident: Ident {
                                                                                        ident: "String",
                                                                                        span: #0 bytes(132..138),
                                                                                    },
                                                                                    arguments: None,
                                                                                },
                                                                            ],
                                                                        },
                                                                    },
                                                                ),
                                                            ),
                                                        ],
                                                        gt_token: Gt,
                                                    },
                                                ),
                                            },
                                        ],
                                    },
                                },
                            ),
                        },
                        Comma,
                        Field {
                            attrs: [],
                            vis: Inherited,
                            ident: Some(
                                Ident {
                                    ident: "current_dir",
                                    span: #0 bytes(145..156),
                                },
                            ),
                            colon_token: Some(
                                Colon,
                            ),
                            ty: Path(
                                TypePath {
                                    qself: None,
                                    path: Path {
                                        leading_colon: None,
                                        segments: [
                                            PathSegment {
                                                ident: Ident {
                                                    ident: "String",
                                                    span: #0 bytes(158..164),
                                                },
                                                arguments: None,
                                            },
                                        ],
                                    },
                                },
                            ),
                        },
                        Comma,
                    ],
                },
            ),
            semi_token: None,
        },
    ),
}

* 其他关卡,完整代码

参考资料:

  • 编辑my_demo/proc_macro_first/Cargo.toml, 添加:
[dependencies]
quote = "1"
syn = { version = "*", features = ["full", "extra-traits"] }
# 新增
proc-macro2 = "1"
  • 编辑my_demo/proc_macro_first/src/lib.rs, 更改为:
extern crate proc_macro;

use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{self, parse_macro_input, Data, DataStruct, DeriveInput, Fields, FieldsNamed, Type};

#[proc_macro_derive(Builder)]
pub fn derive(input: TokenStream) -> TokenStream {
    // Parse the input tokens into a syntax tree.
    let input = parse_macro_input!(input as DeriveInput);

    // Used in the quasi-quotation below as `#name`.
    let name = input.ident;

    let builder = format_ident!("{}Builder", name);

    let data: FieldsNamed = match input.data {
        Data::Struct(DataStruct {
            fields: Fields::Named(n),
            ..
        }) => n,
        other => unimplemented!("{:?}", other),
    };

    let fields = data.named.iter().filter_map(|field| {
        let ty = &field.ty;
        match &field.ident {
            Some(ident) => Some((ident, ty, inner_for_option(ty))),
            _ => None,
        }
    });

    let names = data.named.iter().filter_map(|field| match &field.ident {
        None => None,
        Some(ident) => Some((ident, inner_for_option(&field.ty))),
    });

    let initialize = names.clone().map(|(name, _)| quote! { #name: None });

    let extract = names.clone().map(|(name, option)| match option {
        None => quote! { #name: self.#name.clone()? },
        Some(_) => quote! { #name: self.#name.clone() },
    });

    let quoted_fields = fields.clone().map(|(name, ty, option)| match option {
        None => quote! { #name: Option<#ty> },
        Some(ty) => quote! { #name: Option<#ty> },
    });

    let methods = fields.clone().map(|(name, ty, option)| match option {
        None => quote! {
            pub fn #name(&mut self, value: #ty) -> &mut Self {
                self.#name = Some(value);
                self
            }
        },

        Some(ty) => quote! {
            pub fn #name(&mut self, value: #ty) -> &mut Self {
                self.#name = Some(value);
                self
            }
        },
    });

    let expanded = quote! {
        impl #name {
            fn builder() -> #builder {
                #builder {
                    #(
                        #initialize,
                    )*
                }
            }
        }

        struct #builder {
            #(
                #quoted_fields,
            )*
        }

        impl #builder {
            pub fn build(&self) -> Option<#name> {
                Some(#name {
                    #(
                        #extract,
                    )*
                })
            }

            #(
                #methods
            )*
        }
    };

    TokenStream::from(expanded)
}

fn inner_for_option(ty: &Type) -> Option<Type> {
    match ty {
        Type::Path(syn::TypePath {
            path: syn::Path { segments, .. },
            ..
        }) if segments[0].ident == "Option" => {
            let segment = &segments[0];

            match &segment.arguments {
                syn::PathArguments::AngleBracketed(generic) => {
                    match generic.args.first().unwrap() {
                        syn::GenericArgument::Type(ty) => Some(ty.clone()),
                        _ => None,
                    }
                }
                _ => None,
            }
        }

        _ => None,
    }
}
  • 查看代码生成结果:
# cargo install cargo-expand
cargo expand

生成结果打印如下:

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2018::*;
#[macro_use]
extern crate std;
use proc_macro_first::Builder;
pub struct Command {
    executable: String,
    args: Vec<String>,
    env: Vec<String>,
    current_dir: String,
}
impl Command {
    fn builder() -> CommandBuilder {
        CommandBuilder {
            executable: None,
            args: None,
            env: None,
            current_dir: None,
        }
    }
}
struct CommandBuilder {
    executable: Option<String>,
    args: Option<Vec<String>>,
    env: Option<Vec<String>>,
    current_dir: Option<String>,
}
impl CommandBuilder {
    pub fn build(&self) -> Option<Command> {
        Some(Command {
            executable: self.executable.clone()?,
            args: self.args.clone()?,
            env: self.env.clone()?,
            current_dir: self.current_dir.clone()?,
        })
    }
    pub fn executable(&mut self, value: String) -> &mut Self {
        self.executable = Some(value);
        self
    }
    pub fn args(&mut self, value: Vec<String>) -> &mut Self {
        self.args = Some(value);
        self
    }
    pub fn env(&mut self, value: Vec<String>) -> &mut Self {
        self.env = Some(value);
        self
    }
    pub fn current_dir(&mut self, value: String) -> &mut Self {
        self.current_dir = Some(value);
        self
    }
}
fn main() {
    {
        ::std::io::_print(::core::fmt::Arguments::new_v1(
            &["Hello, world!\n"],
            &match () {
                () => [],
            },
        ));
    };
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions