12 个版本
0.2.0 | 2023 年 5 月 13 日 |
---|---|
0.1.5 |
|
0.1.3 | 2022 年 10 月 25 日 |
0.1.2 | 2022 年 7 月 8 日 |
0.0.1 | 2019 年 9 月 5 日 |
在 Rust 模式 中排名第 38
每月下载量 230,721
用于 213 个 包(40 个直接使用)
31KB
131 行
::macro_rules_attribute
在属性或 derive 位置使用声明式宏。
macro_rules! my_fancy_decorator { /* … */ }
#[apply(my_fancy_decorator!)]
struct Foo { /* … */ }
macro_rules! MyFancyDerive { /* … */ }
#[derive(MyFancyDerive!)] /* using this crate's `#[derive]` attribute */
struct Foo { /* … */ }
动机
点击查看
macro_rules!
宏非常强大,但它们的调用位置可用性有时并不理想,尤其是在装饰项目定义时。
确实,比较
foo! {
struct Struct {
some_field: SomeType,
}
}
到
#[foo]
struct Struct {
some_field: SomeType,
}
-
前者扩展性不佳,因为它会导致向右漂移和“过多的”花括号。
-
但另一方面,后者需要为编译器设置一个专门的包,一个
proc-macro
包。而这99%的情况下会引入::syn
和::quote
依赖项,这些依赖项有 不可忽视的编译时间开销(首次编译时)。-
注意:这些包是优秀的技术作品,可以创建非常强大的宏。当宏的逻辑复杂到需要在
macro_rules!
宏实现时需要递归的tt
咀嚼器,这时使用proc
程序性宏无疑是时候了。任何涉及
ident
生成/推导的内容,例如,通常都需要proc
程序性宏,除非它足够简单,可以使用::paste
处理。
-
解决方案
使用此包的 #[apply]
和 #[derive]
属性,现在可以使用 proc_macro_attribute
语法应用一个 macro_rules!
宏
#[macro_use]
extern crate macro_rules_attribute;
macro_rules! foo {
// …
# ( $($tt:tt)* ) => ()
}
macro_rules! Bar {
// …
# ( $($tt:tt)* ) => ()
}
#[apply(foo)]
#[derive(Debug, Bar!)]
struct Struct {
some_field: SomeType,
}
#
# fn main() {}
无需依赖::quote
、::syn
或::proc-macro2
,以实现"fast compile times"
。
示例
点击查看
更优雅的derives
#[macro_use]
extern crate macro_rules_attribute;
// Easily define shorthand aliases for "derive groups"
derive_alias! {
#[derive(Eq!)] = #[derive(Eq, PartialEq)];
#[derive(Ord!)] = #[derive(Ord, PartialOrd, Eq!)];
#[derive(Copy!)] = #[derive(Copy, Clone)];
#[derive(StdDerives!)] = #[derive(Debug, Copy!, Default, Ord!, Hash)];
}
/// Strongly-typed newtype wrapper around a `usize`, to be used for `PlayerId`s.
#[derive(StdDerives!, Into!, From!)]
pub
struct PlayerId /* = */ (
pub usize,
);
// You can also fully define your own derives using `macro_rules!` syntax
// (handling generic type definitions may be the only finicky thing, though…)
macro_rules! Into {(
$( #[$attr:meta] )*
$pub:vis
struct $NewType:ident (
$(#[$field_attr:meta])*
$field_pub:vis
$Inner:ty $(,
$($rest:tt)* )?
);
) => (
impl ::core::convert::Into<$Inner> for $NewType {
#[inline]
fn into (self: $NewType)
-> $Inner
{
self.0
}
}
)} use Into;
macro_rules! From {(
$( #[$attr:meta] )*
$pub:vis
struct $NewType:ident (
$(#[$field_attr:meta])*
$field_pub:vis
$Inner:ty $(,
$(#[$other_field_attr:meta])*
$other_field_pub:vis
$Rest:ty )* $(,)?
);
) => (
impl ::core::convert::From<$Inner> for $NewType {
#[inline]
fn from (inner: $Inner)
-> Self
{
Self(inner, $($Rest::default),*)
}
}
)} use From;
#
# fn main() {}
有一个proc-macro依赖的轻量级版本,因此需要不便利的macro_rules!
?
假设你正在为async
生态系统编写一个(广泛且)小巧的依赖。
-
由于与
async
一起工作,你很可能需要处理pin-projections,因此也需要处理::pin-project
。 -
但是,由于(广泛且)小巧,你不想依赖更高级proc-macro crate的重型三重奏
quote / proc-macro2 / / syn
。
^[only_full_syn_is_heavy]: (请注意,只有具有"full"
功能的syn
才是真正重型的一方)
因此,你可能需要考虑像::pin-project-lite
这样的东西,以及它基于前者的pin_project!
macro_rules!
填充的#[pin_project]
属性。
但这突然阻碍了类型定义的易用性,更糟糕的是,当模式需要用于其他功能时(例如,一个类似的cell_project!
宏),它将无法组合。
别再说了!是时候#[apply]
我们的巧妙技巧
#[macro_use]
extern crate macro_rules_attribute;
use {
::core::pin::{
Pin,
},
::pin_project_lite::{
pin_project,
},
};
#[apply(pin_project!)]
struct Struct<T, U> {
#[pin]
pinned: T,
unpinned: U,
}
impl<T, U> Struct<T, U> {
fn method(self: Pin<&mut Self>) {
let this = self.project();
let _: Pin<&mut T> = this.pinned; // Pinned reference to the field
let _: &mut U = this.unpinned; // Normal reference to the field
}
}
#
# fn main() {}
更易用的lazy_static!
假设你有一些像这样的东西
# use Sync as Logic;
#
static MY_GLOBAL: &dyn Logic = &Vec::<i32>::new();
现在你想要改变那个MY_GLOBAL
的值,到一个不是const
-constructible的值,但你又想最小化这种改变所带来的开销。
// (For those unaware of it, leaking memory to initialize a lazy static is
// a completely fine pattern, since it only occurs once, and thus, a bounded
// amount of times).
static MY_GLOBAL: &dyn Logic = Box::leak(Box::new(vec![42, 27])); // Error: not `const`!
你可以直接使用lazy_static!
或OnceCell
,但这样你的static
定义现在将比它需要的声音更响亮。是时候对属性位置进行抛光!
首先,定义一个助手,围绕OnceCell
's Lazy
类型
macro_rules! lazy_init {(
$( #[$attrs:meta] )*
$pub:vis
static $NAME:ident: $Ty:ty = $init_value:expr ;
) => (
$( #[$attrs] )*
$pub
static $NAME : ::once_cell::sync::Lazy<$Ty> =
::once_cell::sync::Lazy::new(|| $init_value)
;
)} pub(in crate) use lazy_init;
现在是我们使用它的时候了!
# use Sync as Logic;
#
#[macro_use]
extern crate macro_rules_attribute;
#[apply(lazy_init)]
static MY_GLOBAL: &dyn Logic = Box::leak(Box::new(vec![42, 27]));
#
# macro_rules! lazy_init {(
# $( #[$attrs:meta] )*
# $pub:vis
# static $NAME:ident : $Ty:ty = $init_value:expr ;
# ) => (
# $( #[$attrs] )*
# $pub
# static $NAME : ::once_cell::sync::Lazy<$Ty> =
# ::once_cell::sync::Lazy::new(|| $init_value)
# ;
# )} use lazy_init;
#
# fn main() {}
调试
一个可选的编译特性,"verbose-expansions"
可以在编译时打印出此crate中每个宏调用的确切输出。
[dependencies]
macro_rules_attribute.version = "..."
macro_rules_attribute.features = ["verbose-expansions"]
特性
derive
别名
# fn main() {}
#[macro_use]
extern crate macro_rules_attribute;
derive_alias! {
#[derive(Ord!)] = #[derive(PartialEq, Eq, PartialOrd, Ord)];
}
#[derive(Debug, Clone, Copy, Ord!)]
struct Foo {
// …
}
- 有关
derive_alias!
和#[derive]
的更多信息,请参阅。
cfg
别名
点击查看
# fn main() {}
#[macro_use]
extern crate macro_rules_attribute;
attribute_alias! {
#[apply(complex_cfg!)] = #[cfg(
any(
any(
foo,
feature = "bar",
),
all(
target_os = "fenestrations",
not(target_arch = "Pear"),
),
),
)];
}
#[apply(complex_cfg!)]
mod some_item { /* … */ }
未使用 #[macro_use] extern crate macro_rules_attribute
点击查看
如果您对 #[macro_use]
无限作用域/全局预置语义过敏,您可能不喜欢像以下文档那样广泛使用
#[macro_use]
extern crate macro_rules_attribute;
# fn main() {}
。
在这种情况下,请知悉您完全可以坚持使用 use
导入
use ::macro_rules_attribute::{derive, derive_alias, /* … */};
// or even
use ::macro_rules_attribute::*;
derive_alias! {
#[derive(Copy!)] = #[derive(Clone, Copy)];
}
#[derive(Copy!)]
struct Foo;
甚至内联完全限定路径(但请注意,…_alias!
宏在定义内部仍然使用无限定路径)
::macro_rules_attribute::derive_alias! {
#[derive(Copy!)] = #[derive(Clone, Copy)];
}
#[::macro_rules_attribute::derive(Copy!)]
struct Foo;
我个人认为这些方法太嘈杂,不值得使用,尽管如此获得了“命名空间纯度”,因此我在其他示例中没有使用该模式。