#macro #block #label #catch

named-block

实现从任何块中提前退出的宏

5 个不稳定版本

使用旧 Rust 2015

0.3.1 2017年1月20日
0.3.0 2016年8月13日
0.2.0 2016年5月21日
0.1.1 2016年2月23日
0.1.0 2016年2月23日

Rust 模式 中排名第 988

MIT 许可证

23KB
262

这是什么

这是一个小的 Rust 包,通过一个宏提供了一个新的控制流原语。

RFC 243(?/catch RFC)提议了一个名为“从任何块中提前退出”的功能。它将break泛化,使其可以接受一个表达式和一个生命周期,并在所有{}块中使用,而不仅仅是循环。 break LIFE EXPR 从由生命周期指定的块/循环中退出,并从循环中返回给定的表达式。当然,该表达式必须与块正常结束时返回的值具有相同的类型。

我们可以通过源到源的转换来指定所需的功能(我怀疑如果该功能被添加到语言中,这将不是如何完成的,但这确实表明不需要新的语言功能或控制流原语)

输入

let x = 'a: {
    break 'a 0; // *
    1           // **
};

输出(星号表示对应行)

let x = {
    let ret;
    'a: loop {
        ret = {
            ret = 0;  // *
            break 'a; // *

            1         // **
        };
        break 'a;
    }
    ret
};

嗯,写这个很麻烦(而且读起来也很麻烦)。我们不要再这样做了。

#[macro_use] extern crate named_block;
#[macro_use] extern crate static_cond; // not needed on nightly

let x = block!('a: {
    break 'a 0;
    1
});

修复了!

如何使用它

首先,在 Cargo.toml 中将 "named-block" 添加为依赖项。然后,在您的包根目录顶部添加 #[macro_use] extern crate named_block;

如果您使用的是 nightly Rust,则可以启用 "nightly" Cargo 功能并跳过第二步。否则,您需要在 Cargo.toml 中添加 "static-cond",并添加 #[macro_use] extern crate static_cond;。 (检查此包的 Cargo.toml 以查看应使用哪个版本的 "static-cond"。)

它是如何工作的

block! 通过递归(很多)遍历您的代码,并执行上述描述的源到源转换。变量 ret 使用 hygiene 生成,因此不会与其他变量名或嵌套调用 block! 发生冲突。一些巧妙的宏技巧包括使用“解析栈”进入标记树,并在运行时生成新的宏进行比较。请参阅注释的宏源代码以获取更多详细信息。

限制

  • 该宏会递归。非常多。这意味着它将使编译速度相对于块中代码的长度而减慢。您可能需要增加递归限制(在crate根目录处插入 #![recursion_limit = ""1000""],根据需要调整数字)。

  • break LIFE EXPR 几乎在任何地方出现时都会被转换。

    • 即使它位于另一个宏的调用中,如 block!('a: { foo!(break 'a 42) })。原则上,foo! 可能打算以某种方式转换语法,而 block! 会将其搞砸。但看起来更有可能的是,您确实希望宏调用中的代码被转换。

    • 即使它位于闭包内部。这在罕见情况下可能会导致问题。如果(a)您在 block! 调用内部有一个闭包,并且(b)闭包内部有一个 block! 调用,以及(c)块标签相同...那么您将收到一些古怪的错误消息和/或行为。

    • 该宏足够智能,可以忽略项目。因此,局部 fnimpl 等内的块是安全的。这也应该会加快解析速度--一旦宏看到例如 impl 关键字,它就可以跳过一个整个项目,而无需复制每个标记或下降到标记树。

    • 对于闭包、奇怪的宏或其他未发现的宏错误,有一个特殊的安全出口,形式为属性。任何带有 #[block(ignore)] 注释的标记树将由宏忽略(这不要求 #![feature(stmt_expr_attributes)],因为属性是由宏本身解析的)。

      示例

      block!('a: {
          enum Foo { Bar(i32) }
          let closure = #[block(ignore)] {
              move |Foo::Bar(x): Foo| -> i32 {
                  x + block!('a: {
                      break 'a 41;
                  })
              }
          };
      
          closure(Foo::Bar(1))
      });
      

      此块的结果是 42

  • 缺少特定生命周期的裸 break/continue 语句不允许在 block! 调用中。这是因为宏展开本身会产生一个隐藏的循环,所以这些语句的结果将会是令人困惑且非预期的(类型错误、无限循环等)。同样地,你也不能在 continue 'a(其中 'a 是分配给 block! 的标签)的位置执行。宏在展开过程中会捕获所有这些情况,并产生编译错误。

依赖项