Why does println! inline variable syntax look inconsistent?
Asked Answered
L

2

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;
}

Why does "{a[index_]}" not work? It seems like it should to me.

Longford answered 2/10, 2022 at 20:29 Comment(3)
Just simply it’s the rust syntaxOutpatient
There's only so much the existing println! macro can do. Over time it's likely to get more robust, but parsing arbitrary Rust syntax inside of {} is quite a challenge, so that may be a ways off.Evy
Much as I like string interpolation in Python, this is a truly bizarre way to bring it to the Rust community. It's such a wacky special case, I'm just not seeing it as being worth it.Pillowcase
J
7

The documentation says that this syntax is called "named parameters", and it supports names, not arbitrary expressions.

If a named parameter does not appear in the argument list, format! will reference a variable with that name in the current scope.

a[index_] is not a valid name (but is a valid expression), so you get the error because the format! syntax doesn't let you use arbitrary expressions inside {}, like in Python. Note that println! "uses the same syntax as format!", so the same reasoning applies to println! as well.

Jeanett answered 2/10, 2022 at 20:40 Comment(0)
K
1

To explain why the named parameters syntax is restricted, I'll first note that the "{ident}" syntax existed before Rust 1.58 allowed it to access variables. It was originally designed to work like this:

println!("{name} bought {amount:.2} kg of {nouns}.",
    name = "Richard",
    amount = 5.0,
    nouns = "apples",
);

So the syntax is not new, it was simply relaxed to look for other identifiers in-scope as a fallback if a named parameter wasn't passed to the macro.


They actually can't make the format macros take arbitrary expressions. Of course they can do whatever they like, however it would violate a restriction set elsewhere in the name of forward compatibility.

Let's say I tried to make a macro that worked similarly to the named parameter syntax but allowed for any expression while still allowing for other formatting parameters. Below is a trivial example but the compiler rejects it:

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 `;`

Why is this? If we look at Follow-set Ambiguity Restrictions:

The parser used by the macro system is reasonably powerful, but it is limited in order to prevent ambiguity in current or future versions of the language. In particular, in addition to the rule about ambiguous expansions, a nonterminal matched by a metavariable must be followed by a token which has been decided can be safely used after that kind of match.

As an example, a macro matcher like $i:expr [ , ] could in theory be accepted in Rust today, since [,] cannot be part of a legal expression and therefore the parse would always be unambiguous. However, because [ can start trailing expressions, [ is not a character which can safely be ruled out as coming after an expression. If [,] were accepted in a later version of Rust, this matcher would become ambiguous or would misparse, breaking working code. Matchers like $i:expr, or $i:expr; would be legal, however, because , and ; are legal expression separators. The specific rules are:

  • expr and stmt may only be followed by one of: =>, ,, or ;.

So the developers have not ruled out that : may be some expression trailer or joiner in the future and would like to reserve the syntax if needed. Even if you try to parse it with syn in a procedural macro to work around this restriction, it will actually try to parse as an ExprType (like foo: f64) which is part of the Type Ascription RFC. While that RFC was mostly implemented, it is set to be removed, but I mention it as an example of evolving syntax. If they were to allow the formatting argument to be an arbitrary expression, they'd have to stabilize that : is a valid expression separator.

Unless I'm missing some other syntax, : probably could be stabilized as a separator, and perhaps this is a motivating case. But only the future will tell.

Another hurdle that'd have to be dealt with is that blocks ({ ... }) are also expressions and yet there is an existing syntax for {{ and }} in the formatting macros; they are used for escaping. So if any expression were allowed, then "{{expr}}" would be ambiguous.

Kneepad answered 3/10, 2022 at 1:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.