Assignments inside format_args! are ignored. Instead only the right-hand side of the expression is used.
I tried this code:
fn main() {
let mut x = 1;
println!("first: {}", x = 3);
println!("second: {}", x);
}
or a similar one, using directly format_args:
use std::io::{self, Write};
fn main() {
let mut x = 1;
io::stdout().write_fmt(format_args!("first: {}\n", x = 3));
io::stdout().write_fmt(format_args!("second: {}\n", x));
}
I expected to see this:
first: ()
second: 3
Instead the output was:
first: 3
second: 1
It looks like the the assignment x = 3 is ignored and is instead treated as if it was just 3.
I could only replicate this with format_args! and macros that use it. Other macros, like assert_eq!, work as expected.
rustc --version --verbose:
rustc 1.22.0-nightly (dcbbfb6e8 2017-10-12)
binary: rustc
commit-hash: dcbbfb6e807fdff9c9ba80073bb755f9d9d95e31
commit-date: 2017-10-12
host: x86_64-unknown-linux-gnu
release: 1.22.0-nightly
LLVM version: 4.0
(also tried on rustc 1.20.0-stable and rustc 1.21.0-stable)
format_args supports named arguments with name = value syntax. Assignments can be written as {x = 3} so that they won't be treated as named argument.
What's odd is that {} formats arguments just in the sequence of arguments, ignoring if they are named or positional. Is this intended behavior?
println!("{x} {} {} {}", 1, x = 2, y= 3);
// => 2 1 2 3
The x = 3 is not parsed as assignment, but named parameters.
The bug here is that println!("first: {}", x = 3); should not compile since parameter 0 is missing.
What @kennytm and @sinku said is correct, but I鈥檓 not sure it (the behaviour of how named arguments are considered in context of positional arguments) can be considered a bug anymore. At least I can easily see somebody using this, intentionally or not, even if it wasn鈥檛 documented to have specifically this behaviour. Therefore nominating it for T-lang (since format_args! is a part of language) to decide what the intended behaviour is.
From Positional parameters:
Each formatting argument is allowed to specify which value argument it's referencing, and if omitted it is assumed to be "the next argument".
Which suggests this is working as intended.
If anything I'd say there should be a warning if you name a parameter but never reference it by its name.
@ollie27 Isn't that talking only about positional parameters?
The behavior in question have been introduced in Rust 1.12. I speculate that #33642 (which seems to have added internal translation of named args into positional args) unintentionally did it.
Example:
fn main() {
println!("{}", x = 1);
}
Rust 1.11.0:
<std macros>:3:11: 3:36 error: invalid reference to argument `0` (no arguments given)
<std macros>:3 print ! ( concat ! ( $ fmt , "\n" ) , $ ( $ arg ) * ) ) ;
^~~~~~~~~~~~~~~~~~~~~~~~~
<std macros>:3:11: 3:36 note: in this expansion of concat!
<std macros>:2:27: 2:58 note: in this expansion of format_args!
<std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>)
test.rs:2:5: 2:27 note: in this expansion of println! (defined in <std macros>)
test.rs:2:24: 2:25 error: named argument never used
test.rs:2 println!("{}", x = 1);
^
error: aborting due to 2 previous errors
Rust 1.12.0
(compiles fine, and the program prints "1")
Consensus in the lang team meeting is that we should reintroduce a lint for "named argument never used". And if it's just a lint, we don't need a full crater run.
Most helpful comment
@ollie27 Isn't that talking only about positional parameters?
The behavior in question have been introduced in Rust 1.12. I speculate that #33642 (which seems to have added internal translation of named args into positional args) unintentionally did it.
Example:
Rust 1.11.0:
Rust 1.12.0