31个版本
0.5.1 | 2024年7月4日 |
---|---|
0.5.0 | 2023年10月10日 |
0.4.3 |
|
0.4.2 | 2023年7月24日 |
0.2.11 | 2023年3月31日 |
#16 in 过程宏
168,072 每月下载量
在 340 个crate中使用 (13直接使用)
65KB
961 行
Macro Magic 🪄
此crate提供 #[export_tokens]
宏以及一系列伴随宏,包括 #[import_tokens_proc]
和 #[import_tokens_attr]
宏。当与 #[export_tokens]
结合使用时,这些宏允许您创建常规和属性过程宏,在这些宏中,您可以通过名称/路径引用来导入和使用在其他模块、文件甚至其他crate中标记为 #[export_tokens]
的外部/外部项的标记。
除此之外,macro_magic
引入的模式还可以用于在相同文件内以及跨文件和crate边界安全高效地导出和导入项标记。唯一的要求是您可以控制(即可以向两个位置的源代码添加属性宏)。
macro_magic
被设计用于稳定版Rust,并且完全兼容 no_std
(事实上,有一个单元测试来确保一切都是 no_std
安全的)。
通用语法
您可以使用 macro_magic
构建如下所示的常规和属性过程宏
#[my_attribute(path::to::MyItem)]
trait SomeTrait {
// ..
}
这个
do_something!(path::to::MyItem);
或者甚至是这个
let foreign_tokens = my_macro!(path::to::MyItem);
assert_eq!(foreign_tokens.to_string(), "struct MyItem {...}");
其中 path::to::MyItem
是指向一个标记有 #[export_tokens]
的项的路径。
所有这些行为都是在幕后使用 proc 宏创建的,这些宏基于 macro_rules
创建回调,但作为程序员,这种复杂性完全通过你可以应用于 proc 宏的简单属性宏来隐藏,从而赋予它们基于路径导入外部项标记的能力。
属性示例
你可以编写一个属性宏,将一个结构体的字段“注入”到另一个结构体中,如下所示
#[import_tokens_attr]
#[proc_macro_attribute]
pub fn combine_structs(attr: TokenStream, tokens: TokenStream) -> TokenStream {
let foreign_struct = parse_macro_input!(attr as ItemStruct);
let local_struct = parse_macro_input!(tokens as ItemStruct);
let Fields::Named(local_fields) = local_struct.fields else {
return Error::new(
local_struct.fields.span(),
"unnamed fields are not supported"
).to_compile_error().into()
};
let Fields::Named(foreign_fields) = foreign_struct.fields else {
return Error::new(
foreign_struct.fields.span(),
"unnamed fields are not supported"
).to_compile_error().into()
};
let local_fields = local_fields.named.iter();
let foreign_fields = foreign_fields.named.iter();
let attrs = local_struct.attrs;
let generics = local_struct.generics;
let ident = local_struct.ident;
let vis = local_struct.vis;
quote! {
#(#attrs)
*
#vis struct #ident<#generics> {
#(#local_fields),
*
,
#(#foreign_fields),
*
}
}
.into()
}
然后你可以像这样使用 #[combine_structs]
属性
#[export_tokens]
struct ExternalStruct {
foo: u32,
bar: u64,
fizz: i64,
}
#[combine_structs(ExternalStruct)]
struct LocalStruct {
biz: bool,
baz: i32,
}
这将导致以下展开输出 LocalStruct
struct LocalStruct {
foo: u32,
bar: u64,
fizz: i64,
biz: bool,
baz: i32,
}
请注意,由于 #[import_tokens_attr]
的力量,combine_structs
proc 宏上的 attr
变量将接收 ExternalStruct
项的实际标记,而不仅仅是接收 ExternalStruct
路径的标记。
这让你能够编写接收两个项标记的属性宏,一个通过第一个参数 attr
指定,以及通过第二个参数 tokens
指定的属性附加项的标记。唯一的要求是 attr
指定的项已标记为 #[export_tokens]
。
Proc 宏示例
你可以编写一个 PHP/ruby/crystal 风格的文本导入/require
宏,该宏盲目地将指定外部模块的标记导入当前上下文(具有所有好与坏的含义),如下所示
#[import_tokens_proc]
#[proc_macro]
pub fn require(tokens: TokenStream) -> TokenStream {
let external_mod = parse_macro_input!(tokens as ItemMod);
let Some((_, stmts)) = external_mod.content else {
return Error::new(
external_mod.span(),
"cannot import tokens from a file-based module since custom file-level \
attributes are not yet supported by Rust"
).to_compile_error().into()
};
quote! {
#(#stmts)
*
}
.into()
}
然后你可以像这样使用 require!
宏
// in some external crate
#[export_tokens]
mod an_external_module {
fn my_cool_function() -> u32 {
567
}
fn my_other_function() -> u32 {
116
}
}
// in another crate where we will use the `require!` macro
mod my_module {
use my_macros::require;
fn existing_stuff() {
println!("foo!");
}
require!(external_crate::an_external_module);
}
这将导致在 my_module
中 require!
的以下展开
mod my_module {
use my_macros::require;
fn existing_stuff() {
println!("foo!");
}
fn my_cool_function() -> u32 {
567
}
fn my_other_function() -> u32 {
116
}
}
请注意,这个假设的 require!
宏有两个危险之处
- 你可能通过在外国模块中使用的
use
语句引入了任何类型,这些类型可能在新的上下文中可用,也可能不可用,这取决于是否有额外的使用语句。 - 如果你在
require!
宏中使用的模块或上下文中现有的项与你导入的项冲突,你会得到编译器错误(尽管这是好事)。
这些只是 macro_magic
的 一些 功能 🪄。
有关更多信息,请参阅 docs
。
功能
proc_support
在利用任何导入令牌功能(包括 #[import_tokens_attr]
、#[import_tokens_proc]
和 import_tokens!
)的 proc 宏 crate 中,必须启用 proc_support
功能。否则,这些宏将无法正常工作,并会引发编译器错误,抱怨在 macro_magic::mm_core
下不存在项目。宏 #[export_tokens]
不需要此功能即可正常工作,因此您可以安全地使用它而无需启用此功能。
此功能门控的原因是,像 syn
、quote
、proc_macro2
等功能并不完全兼容 no_std
,并且仅应在 proc 宏 crate 中启用。因此,您不应该在仅使用 #[export_tokens]
而没有其他内容的 crate 中启用此功能。
限制
macro_magic
无法 提供在多个宏调用之间建立状态信息的能力,但可以使用 外部宏模式 或在某些情况下,在您的 proc 宏 crate 中使用静态原子和互斥锁(我们在实际中就是这样做的,以跟踪唯一标识符)来有效地解决这个问题。
破坏性变更
- 0.4x 删除了
#[use_attr]
和#[use_proc]
(它们在新 0.4x 版本中不再需要),并且还删除了在函数内部和模块权限边界(例如在不可访问的私有模块中)等不可访问位置访问#[export_tokens]
调用的能力。如果感兴趣,此功能可能会在未来重新添加,但删除它使我们能够统一我们的macro_rules!
声明命名,并消除对#[use_attr]
/#[use_proc]
的需求。 - 0.2x 删除或重写了一些依赖于
OUT_DIR
中写入/读取文件的非未来兼容行为的特性。版本 >= 0.2.0 完全安全,不再包含此行为。然而,提供在命名空间中枚举所有#[export_tokens]
调用的能力的特性已被删除。正确的方法是使用上述的外部宏模式或在您的 proc 宏 crate 中使用全局状态互斥锁/原子,如前所述。
更详细的历史变更信息可以在 发布页面 找到。
依赖项
~0.4–1MB
~21K SLoC