「这是我参与2022首次更文挑战的第 19 天,活动详情查看:2022首次更文挑战」。
macro_rules! ok_or_return{
// internal rule.
(@error $a:ident,$($b:tt)* )=>{
{
match $a($($b)*) {
Ok(value)=>value,
Err(err)=>{
return Err(err);
}
}
}
};
// public rule can be called by the user.
($a:ident($($b:tt)*))=>{
ok_or_return!(@error $a,$($b)*)
};
}
fn some_work(i: i64, j: i64) -> Result<(i64, i64), String> {
if i + j > 2 {
Ok((i, j))
} else {
Err("error".to_owned())
}
}
fn main() -> Result<(), String> {
// instead of round bracket curly brackets can also be used
ok_or_return! {some_work(1,4)};
ok_or_return!(some_work(1, 0));
Ok(())
}
复制代码
高级解析
宏有时会执行一些需要解析Rust语言本身语法的任务。
现在把我们到目前为止所涉及的所有概念放在一起。下面我们创建一个宏,通过 pub
关键字使一个结构变得公开。
首先,我们需要解析 Rust struct
,获得 struct
的 name, fields, field type
。
解析 name 和 field
struct
声明在开始时有一个可见性关键字(如pub),接着是 struct
自身关键字,然后是 struct
的名称和 struct
的内容:
macro_rules! make_public {
(
// use vis type for visibility keyword and ident for struct name
$vis:vis struct $struct_name:ident { }
) => {
{
pub struct $struct_name{ }
}
}
}
复制代码
$vis
→ 可见性$struct_name
→ 结构名称
要使一个 struct
公开,我们只需要返回添加pub关键字的结果,并忽略 $vis
变量之前的值。
一个 struct
可以包含不同的数据类型和可见性的多个字段。
- ty标记类型用于数据类型
- vis用于可见性
- ident用于字段名
- 零个或多个字段使用
*
重复的方式
macro_rules! make_public {
(
$vis:vis struct $struct_name:ident {
$(
// vis for field visibility, ident for field name and ty for field data type
$field_vis:vis $field_name:ident : $field_type:ty
),*
}
) => {
{
pub struct $struct_name{
$(pub $field_name : $field_type,)*
}
}
}
}
复制代码
解析元数据
通常情况下,struct
会被附加一些元数据或派生宏(过程宏的一种),例如 #[derive(Debug)]
。这些元数据需要保持完整。而解析这些元数据是使用 meta
类型完成的。
macro_rules! make_public{
(
// meta data about struct
$(#[$meta:meta])*
$vis:vis struct $struct_name:ident {
$(
// meta data about field
$(#[$field_meta:meta])*
$field_vis:vis $field_name:ident : $field_type:ty
),*$(,)+
}
) => {
{
$(#[$meta])*
pub struct $struct_name{
$(
$(#[$field_meta:meta])*
pub $field_name : $field_type,
)*
}
}
}
}
复制代码
make_public!
现在已经准备好了。为了看看 make_public!
是如何工作的,让我们在 Rust Playground 中将宏使用到实际编译的代码中:
fn main(){
make_public!{
#[derive(Debug)]
struct Name{
n:i64,
t:i64,
g:i64,
}
}
}
复制代码
而宏扩展代码如下:
macro_rules! make_public {
($ (#[$ meta : meta]) * $ vis : vis struct $ struct_name : ident
{
$
($ (#[$ field_meta : meta]) * $ field_vis : vis $ field_name : ident
: $ field_type : ty), * $ (,) +
}) =>
{
$ (#[$ meta]) * pub struct $ struct_name
{
$
($ (#[$ field_meta : meta]) * pub $ field_name : $
field_type,) *
}
}
}
fn main() {
pub struct name {
pub n: i64,
pub t: i64,
pub g: i64,
}
}
复制代码
声明宏的局限性
声明式宏有一些限制。有些与Rust宏本身有关,有些则是针对声明式宏的:
- 缺少对宏的自动完成和扩展的支持
- 调试声明式宏很困难
- 修改能力有限
- 会生成较大的二进制文件
- 较长的编译时间(这同时适用于声明性和过程宏)