3

我开始使用 Rust 宏,我来尝试这个小练习示例。我想定义一个宏,该宏扩展为类型i32(例如,但不是很重要)的变量初始化(名称无关紧要)和对该变量的一系列操作,在这种情况下为 avar += 1或 avar -= 1最后它将调用println!("{}", var). 该宏将采用一系列基于+-匹配上述操作的标记。

例如:

operate_integer![+++---]

将扩展为:

let mut var: i32 = 0;
var += 1;
var += 1;
var += 1;
var -= 1;
var -= 1;
var -= 1;
print!("{}", var);

我决定为此使用 2 个宏,一个用于包装初始化和打印,另一个用于评估+-令牌:

基础是:

macro_rules! operate_integer {
    // $($all_tokens:tt)* should match everything, it will be forward to the helper macro
    ($($all_tokens:tt)*) => {
        let mut var : i32 = 0;
        operate_integer_helper![$($all_tokens:tt)*]
        print!("{}", var);
    }
}

助手将扩展操作:

macro_rules! operate_integer_helper {
    // the idea is that it matches a `+` followed by more tokens
    (+$($t:tt)*) => {
        val += 1;
        operate_integer_helper![$($t:tt)*] // we recursively handle the remaining tokens
    }

    (-$($t:tt)*) => {
        val -= 1;
        operate_integer_helper![$($t:tt)*]
    }
}

这当然不起作用,编译失败并出现以下错误(Playground):

error: no rules expected the token `(`
   --> src/lib.rs:102:5
    |
102 |     (+$($t:tt)*) => {
    |     ^ no rules expected this token in macro call

我有点卡住了。我知道自从我刚开始以来我可能会遗漏很多概念,我真的很感激能帮助我理解如何使用宏。先感谢您!

4

1 回答 1

3

你其实很亲近!只剩下几个小错误。(如果您想了解有关宏的更多信息,请一次只阅读一个要点,然后尝试从那里自行进步!

  • 使用(重复)元变量时,您无需再次指定元变量类型。所以它$($t:tt)*在宏的模式中,但是如果你想使用它,它就是$($t)*

  • 如果宏定义中有多个规则,则需要以分号结束每个规则。

    macro_rules! {
        (+ $(t:tt)*) => { ... };
        (- $(t:tt)*) => { ... };
    }
    
  • Rust 编译器总是需要知道您是否要将宏扩展为表达式或语句。由于您生成的是语句列表而不是单个表达式,因此您必须在宏的调用中添加分号!这意味着,在main()您的宏定义中,所有宏调用都包含辅助宏。

  • 由于是的宏调用创建了一个新的语法上下文,并且所有标识符(名称)只能在它们的语法上下文中访问,因此辅助宏不能使用var(即使在修复了拼写错误val->之后var)。因此,您必须将该名称传递给辅助宏:

    macro_rules! operate_integer {
        ($($all_tokens:tt)*) => {
            let mut var: i32 = 0;
            operate_integer_helper![var $($all_tokens)*];  // <-- pass identifier 
            println!("{}", var);
        }
    }
    
    macro_rules! operate_integer_helper {
        ($var:ident +$($t:tt)*) => {              // <- accept identifier
            $var += 1;                            // <- use identifier
            operate_integer_helper![$var $($t)*]
        };
    
        ($var:ident -$($t:tt)*) => {
            $var -= 1;
            operate_integer_helper![$var $($t)*]
        };
    }
    
  • 完成所有操作后,您会收到错误“宏调用意外结束”。这是因为您没有递归停止规则!所以你必须在你的辅助宏中添加一个新规则:($var:ident) => {};. 当只有名称而没有+or-标记时使用此规则。

现在:它起作用了!

我仍然会改变最后一件事:通常拥有第二个辅助宏不是一个好主意,因为该宏可能不在调用主宏的范围内。相反,通常使用内部规则。您可以在此处阅读有关这些内容的更多信息。

有了这个,这就是结果代码

于 2019-05-04T09:09:53.007 回答