macro_rules! first {
{ // * 📝←左边的括号只是标注「推荐用括弧」而对实际解析无限定作用
$guardian_1:expr => $value_1:expr , // ! ←此处必须要逗号分隔表达式,避免解析歧义
$( $guardian:expr => $value:expr , )* // ! 逗号仍然必要
_ => $value_else:expr $(,)? // ←可选的尾后逗号
// ↑对字面标识「_」无需`$(...)`引用
// ! ↑但不能把`_ => `标注为可选:local ambiguity when calling macro `first`: multiple parsing options: built-in NTs expr ('value_else') or expr ('guardian').
} => { ... };
{ // * 📝←左边的括号只是标注「推荐用括弧」而对实际解析无限定作用
// * ↓🚩此处直接使用令牌树语法,然后在解析时强制使用圆括号解包
// * ✨好处:无需考虑里边的内容(兼容任何`f(x)`语法),只要在展开时能拼上就行
$f_guardian:tt => $f_value:tt;
$guardian_1:expr => $value_1:expr , // ! ←此处必须要逗号分隔表达式,避免解析歧义
$( $guardian:expr => $value:expr , )* // ! 逗号仍然必要
_ => $value_else:expr $(,)? // ←可选的尾后逗号
// ↑对字面标识「_」无需`$(...)`引用
// ! ↑但不能把`_ => `标注为可选:local ambiguity when calling macro `first`: multiple parsing options: built-in NTs expr ('value_else') or expr ('guardian').
} => { ... };
( @VALUE _ $value:expr ) => { ... };
( @VALUE (_) $value:expr ) => { ... };
( @VALUE ($($f:tt)+) $value:expr ) => { ... };
}Expand description
§first!:匹配首个判据,并返回其值
- 🎯用于简写「截断性判断」结构
- 📌可用于简写
if-else if-else「优先分支」结构 - 📌可用于简写
match 0 {_ if XXX => Z1, _ if YYY => Z2, _ => ELSE}「伪优先分支」结构
- 📌可用于简写
📝Rust的「规则宏」并不能被视作为一个类似「变量」「函数」之类能导出的量
- ❌无法使用常规的
pub(相当于Julia的export)导出- 📌需要使用
#[macro_export]导出- 📝可选的[
local_inner_macros]:导出在当前模块中定义的「内部宏」(inner macro)。- 内部宏:仅在其他宏的定义体中使用的宏
- 📝可选的[
- 📌需要使用
- ❗需要在crate层级导入,而非在定义宏的模块中导入
- 📝使用
#[cfg(not(test))]标注「非测试」- 🎯可防止「定义之前测试宏」导致的「文档测试(doc test)失败」
- ❗但也会导致在别的测试中用不了
- 📌SOLUTION:在文档代码块中引入
use 【库名】::*;- ❗不能用
crate|help: consider importing this macro
- ❗不能用
§用法
§常规用法
use nar_dev_utils::first;
fn see(v: &str) -> &str {
first! {
v.is_empty() => "空的!",
v.starts_with('0') => "以零开头!",
v.starts_with('1') => "以一开头!",
v.starts_with('2') => "以二开头!",
v.len() > 5 => "超长字符串!",
v.starts_with('3') => "以三开头!",
_ => "这啥玩意…", // fallback
}
}将被转换成
fn see(v: &str) -> &str {
if v.is_empty() {
"空的!"
} else if v.starts_with('0') {
"以零开头!"
} else if v.starts_with('1') {
"以一开头!"
} else if v.starts_with('2') {
"以二开头!"
} else if v.len() > 5 {
"超长字符串!"
} else if v.starts_with('3') {
"以三开头!"
} else {
"这啥玩意…" // fallback
}
}§结合「预处理函数」实现批量应用
use nar_dev_utils::first;
fn see(v: &str) -> String {
first! {
// 格式:`(预处理输入) => (预处理输出)`
(v.starts_with) => (str::to_string);
'0' => "以零开头!",
'1' => "以一开头!",
'2' => "以二开头!",
'3' => "以三开头!",
_ => "这啥玩意…", // fallback
}
}
/// 可以用 `_` 跳过预处理
/// * 🎯debug @ 多余的逗号
fn see_bool(v: bool) -> &'static str {
first! {
// 格式:`(预处理输入) => (预处理输出)`
(_) => (_); // * 🚩相当于原样输出
v => "真!", // * 🚩只有`v == true`时才进入此分支
_ => "假!", // fallback
}
}将被转换成
fn see(v: &str) -> String {
// 预处理输出展开:输出统一用`str::to_string`包裹
str::to_string(
// 预处理输入展开:统一插入`v.starts_with`
if v.starts_with('0') {
"以零开头!"
} else if v.starts_with('1') {
"以一开头!"
} else if v.starts_with('2') {
"以二开头!"
} else if v.starts_with('3') {
"以三开头!"
} else {
"这啥玩意…" // fallback
}
)
}§可以用 _ 跳过预处理
use nar_dev_utils::first;
/// * 🎯debug @ 多余的逗号
fn see_bool(v: bool) -> &'static str {
first! {
// 格式:`(预处理输入) => (预处理输出)`
(_) => (_); // * 🚩相当于原样输出
v => "真!", // * 🚩只有`v == true`时才进入此分支
_ => "假!", // fallback
}
}§用例
use nar_dev_utils::{first, show, asserts};
let v: &str = "1";
// 测试1 不安排「预处理函数」 | 匹配一个无意义的值,使用匹配守卫来确定「唯一进入的分支」
let v = first! {
v.is_empty() => "空的!",
v.starts_with('0') => "以零开头!",
v.starts_with('1') => "以一开头!",
v.starts_with('2') => "以二开头!",
v.len() > 5 => "超长字符串!",
v.starts_with('3') => "以三开头!",
_ => "这啥玩意…",
};
// 测试2 使用「成员方法」预处理被匹配项 | 此时 v == "以一开头!"
let v2 = first! {
(v.starts_with) => (_);
'0' => "以零开头!",
'1' => "以一开头!",
'2' => "以二开头!",
'3' => "以三开头!",
_ => "这啥玩意…",
};
// 测试3 使用「闭包」处理被匹配项,同时使用「路径」处理匹配值 | 此时 v2 == "这啥玩意…"
let clj = |c| v2.contains(c);
let v3 = first! {
(clj) => (str::to_string);
'这' => "「这」在里头!",
'啥' => "「啥」在里头!",
'玩' => "「玩」在里头!",
'意' => "「意」在里头!",
_ => "这啥玩意…",
};
// 测试4 使用「属性」同时处理被匹配项和匹配值
struct F<I, R>(Box<dyn Fn(I) -> R>);
let f = F(Box::new(clj)); // 检测的闭包
let f2 = F(Box::new(Box::new)); // 装箱的闭包
let v4 = first! {
(f.0) => (f2.0);
'这' => "「这」在里头!",
'啥' => "「啥」在里头!",
'玩' => "「玩」在里头!",
'意' => "「意」在里头!",
_ => "这啥玩意…",
};
// 展示&断言
asserts! {
show!(first! {@VALUE (1.cmp) &2}) => std::cmp::Ordering::Less
show!(first! {@VALUE (std::any::type_name_of_val) &2}) => "i32"
show!(v) => "以一开头!"
show!(v2) => "这啥玩意…"
show!(v3) => "「这」在里头!".to_string()
show!(v4) => Box::new("「这」在里头!")
}