在Rust的“if”条件语句中是否可以使用宏展开? 能否在Rust的“if”条件语句中使用宏展开?

3
例如,想象一下一个理论上的lets!宏,它会像这样编写:
if lets![Some(x) = x && Some(y) = y] {
  ...
}

这将扩展为类似于:

if let Some(x) = x {
  if let Some(y) = y {
    ...
  }
}

我知道if-let-chains即将到来,所以这个问题并非特指let链。相反,它更多地涉及提供最终不具备卫生性的宏,因此,在宏扩展之后应该仅解析生成的代码--例如,想象一下宏填写if条件的一部分,并影响扩展后代码的解析方式。

在Rust中是否可能实现这样的东西?


宏能否像这样一样实现呢?虽然我还不是 Rustacean,但根据我所了解的,我认为宏应该能够做到这一点。 - zenly
这样的事情可能通过在整个语句/表达式上使用属性过程宏来实现。 - PitaJ
if let (Some(x), Some(y)) = (x, y) { 是一种选项。 - loganfsmyth
@caTS 如果你在谈论两个 if let 通过 && 连接的情况,那么这是RFC的一部分,目前在Rust中还不稳定。它即将到来,但除非你愿意使用夜间版本,否则它还没有到达。 - Silvio Mayolo
1个回答

3

理论上是可能的,但你选择了宏中比较困难的边缘情况。主要原因是这个宏无法在不修改宏范围外的区域的情况下展开为所需的等效代码。让我们用一个稍微修改过的示例版本来说明这个问题。

if lets![Some(x) = a && Some(y) = b] {
    // etc
}

这似乎很显然,但无法将其扩展到两个嵌套的if语句,因为这将需要超出宏范围的额外花括号。

if let Some(x) = a {
    if let Some(y) = b {
        // etc
    }  // <- How do we create this brace?
}

简单的解决方法是将两个匹配项合并为一个。但是,通过提前评估两个表达式,我们会失去短路属性。不幸的是,这也不可能,因为if let被认为是一个特殊表达式,而发出let x = y本身就不是有效的表达式。虽然未来可能会改变这种情况,但我对此表示怀疑,因为它将需要允许在任意表达式中声明变量。

// Theoretical macro expansion
if let (Some(x), Some(y)) = (a, b) {
    // etc
}

我已经思考了一段时间,但我无法想出一种解决方案,可以弥补事实,即if let是一个特殊运算符,我们需要发出一些在if{/*等等*/}之间的令牌,这些令牌可以用作if let。即使过程宏具有发射任何标记的完全访问权限,他们可能也不能按照您描述的方式执行此操作。方便地,最近在[rust]标签下提出的问题 (Rust Procedural Macro - macro expansion ignores token `,`) 对此提供了一些见解。过程宏不能只发射任意标记并期望被放置到组装的代码中。它们必须在使用它们的上下文中有效。例如,我们不希望宏添加未匹配的括号或多个逗号分隔的值,就像链接的问题中一样。宏需要涵盖整个if语句才能正确扩展。

实际上,这很可能是流行的if_chain框架以其工作方式而闻名的原因。如果您想要这个功能,并且不想添加夜间功能或花费太多时间来弄清楚如何使其工作,那么这个框架可能是最好的选择。这里是他们文档中的一个快速例子,展示了如何使用他们的宏。

if_chain! {
    if let Some(y) = x;
    if y.len() == 2;
    if let Some(z) = y;
    then {
        do_stuff_with(z);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接