为什么println!内联变量语法看起来不一致?

7
let a = [10, 20, 30, 40, 50];
let mut index_ = 0;
while index_ < 5 {
    println!("{}", a[index_]); // works
    println!("{a[index_]}");   // does not work
    println!("{index_}");      // works
    println!("{}", index_);    // works
    index_ = index_ + 1;
}

为什么 "{a[index_]}" 不能用?在我看来应该可以。

1
只是简单地说,它就是Rust语法。 - Daniel A. White
现有的 println! 宏所能做的事情是有限的。随着时间的推移,它可能会变得更加强大,但在 {} 内解析任意 Rust 语法是相当具有挑战性的,因此这可能还有一段路要走。 - tadman
2
虽然我很喜欢Python中的字符串插值,但这种方式真的很奇怪,不适合在Rust社区中推广。这是一个非常古怪的特例,我认为它并不值得这样做。 - Silvio Mayolo
2个回答

7

文档中提到,这种语法称为“命名参数”,它支持使用名称,而不是任意表达式。

如果命名参数在参数列表中不存在,format!将引用当前作用域中具有该名称的变量。

a [index_]不是一个有效的名称(但是是一个有效的表达式),因此您会得到错误,因为format!语法不允许在{}内部使用像Python中那样的任意表达式。请注意,println!"使用与format!相同的语法", 因此对于println!也适用相同的推理。


0
为了解释为什么命名参数语法受限,我首先要指出在Rust 1.58允许访问变量之前,"{ident}"语法已经存在。它最初的设计是这样工作的:
println!("{name} bought {amount:.2} kg of {nouns}.",
    name = "Richard",
    amount = 5.0,
    nouns = "apples",
);

所以语法并不新奇,只是为了寻找作为备选项的其他在范围内的标识符而放松了规定,如果未向宏传递命名参数。


实际上,他们不能使格式宏接受任意表达式。当然他们可以做任何他们想做的事情,但这将违反其他地方设定的前向兼容性限制。

假设我尝试创建一个类似于命名参数语法但允许任何表达式的宏,同时仍允许其他格式化参数。以下是一个微不足道的示例,但编译器会拒绝它:

macro_rules! my_format {
    ($e:expr : $width:literal) => {
        // ..
    };
}

fn main() {
    my_format!(expr:2);
}

error: `$e:expr` is followed by `:`, which is not allowed for `expr` fragments
 --> src/main.rs:2:14
  |
2 |     ($e:expr : $width:literal) => {
  |              ^ not allowed after `expr` fragments
  |
  = note: allowed there are: `=>`, `,` or `;`

为什么会这样呢?如果我们看一下Follow-set Ambiguity Restrictions

宏系统使用的解析器相当强大,但为了防止当前或未来版本的语言中出现歧义而受到限制。特别是,除了关于模糊扩展的规则之外,由元变量匹配的非终端符号必须后跟一个已确定可以安全使用的标记,以便在这种匹配之后使用。
例如,像$i:expr [ , ]这样的宏匹配器在 Rust 中理论上可以被接受,因为[,]不能成为合法表达式的一部分,因此解析总是不含歧义的。然而,由于[可以开始尾随表达式,[不是一个可以安全地排除在表达式后面的字符。如果在 Rust 的后续版本中接受了[,],则此匹配器将变得模糊或解析错误,从而破坏工作代码。像$i:expr,$i:expr;这样的匹配器是合法的,因为,;是合法的表达式分隔符。具体规则如下:
  • exprstmt只能后跟以下之一:=>,;

因此,开发人员并没有排除:在未来可能成为某种表达式尾缀或连接符,并希望保留该语法以备不时之需。即使您尝试使用syn在过程宏中解析它以绕过此限制,它实际上将尝试解析为ExprType(例如foo: f64),这是类型注释RFC的一部分。虽然该RFC已经大部分实现,但将被删除,但我提到它作为语法演变的例子。如果他们允许格式化参数是任意表达式,他们必须稳定:是有效的表达式分隔符。

除非我错过了其他语法,否则:可能会被稳定为分隔符,也许这是一个激励案例。但只有未来才能告诉我们。

另一个需要解决的障碍是块({ ... })也是表达式,但格式化宏中已经存在{{}}的语法;它们用于转义。因此,如果允许任何表达式,则"{{expr}}"将是模棱两可的。


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