first

Macro first 

Source
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("「这」在里头!")
}