Take something like the following format string:
format!("{a:?} is {a}", a = "foo\u2014bar")
I _expect_ this to produce the string "foo\u2014bar" is foo—bar
.
However, what it actually produces is "foo\u2014bar" is "foo\u2014bar"
; the behaviour of the second placeholder has changed from using Default
to using Poly
.
If I make the type difference explicit with the format string "{a:?} is {a:s}"
, then I get a compilation error, which, although not what I wanted, at least _tells_ me I can't do what I'm trying to do:
0.rs:2:50: 2:64 error: argument redeclared with type `s` when it was previously `?`
0.rs:2 println!("a is {a:?} (literally, {a:s})", a = "foo\u2014bar");
^~~~~~~~~~~~~~
error: aborting due to previous error
My problem is with the implicit change of behaviour for named arguments from using Default
to using what was done last. This distinctly confused me when I came across it. If using an argument with different formatting constraints is not possible (I don't see why this constraint is there, truth to tell) then I believe the unspecified format should still be interpreted as Default
rather than what-was-done-last, and an error raised rather than this surprising behaviour.
Hmm, I agree with you. When I originally wrote this I wasn't intending on having {}
be the prevalent format, but that appears to be how it's evolved.
I suppose it would be kinda nice to be able to use the same argument with different formatting constraints, and there's not really a reason that it's not done except for the fact that it's not implemented. It'll involve a little bit of trickery, but none of the arguments are moved anyway so there's no worries there.
I'm going to remove this coercion for now, but I like the idea of using multiple formats, so I'm renaming the title of this issue to be more appropriate for the desired feature.
Previous title was: "Formatting for named arguments persists to later mentions, leading to confusion"
Triage: all the same today.
Rust by Examples brings me here when I was trying to
impl Display for Color {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "RGB ({red}, {green}, {blue}) 0x{red:02X}{green:02X}{blue:02X}", red = self.red, green = self.green, blue = self.blue)
}
}
I'd like to take this, will have the time for contributing in about a week. Fixing this would take us to almost the same level of Python in terms of ergonomics, and I see no reason to not do it!
Just investigated this a little, seems the culprit is that format_args!
associates each of the placeholders (both positional and named) with exactly one type, even going as far as verify_same
-ing them.
I would fix this by introducing a level of indirection where every unique value-type combination appeared is evaluated only once, left-to-right to build the Arguments
. No other modifications seems necessary.
Any suggestions?
Wow, I didn't know about format!("{var_name}", var_name = xxx)!
Is this documented anywhere?
I've made some progress:
fn main() {
println!("{a:?} is {a}; {0:?} is {0}", "测试å—符串", a="foo\u{2014}bar");
}
now expands to (some irrelevant parts omitted for brevity):
// prelude omitted
fn main() {
::std::io::_print(::std::fmt::Arguments::new_v1_formatted({
static __STATIC_FMTSTR:
&'static [&'static str]
=
&["",
" is ",
"; ",
" is ",
"\n"];
__STATIC_FMTSTR
},
&match (&"\u{6d4b}\u{8bd5}\u{5b57}\u{7b26}\u{4e32}",
&"foo\u{2014}bar")
{
(__arg0,
__arga) =>
[::std::fmt::ArgumentV1::new(__arg0,
::std::fmt::Debug::fmt),
::std::fmt::ArgumentV1::new(__arg0,
::std::fmt::Display::fmt),
::std::fmt::ArgumentV1::new(__arga,
::std::fmt::Debug::fmt),
::std::fmt::ArgumentV1::new(__arga,
::std::fmt::Display::fmt)],
},
{
static __STATIC_FMTARGS:
&'static [::std::fmt::rt::v1::Argument]
=
&[::std::fmt::rt::v1::Argument{position:
::std::fmt::rt::v1::Position::At(2usize),
format: (omitted),},
::std::fmt::rt::v1::Argument{position:
::std::fmt::rt::v1::Position::At(3usize),
format: (omitted),},
::std::fmt::rt::v1::Argument{position:
::std::fmt::rt::v1::Position::At(0usize),
format: (omitted),},
::std::fmt::rt::v1::Argument{position:
::std::fmt::rt::v1::Position::At(1usize),
format: (omitted),}];
__STATIC_FMTARGS
}));
}
which produces the desired result
"foo\u{2014}bar" is foo—bar; "\u{6d4b}\u{8bd5}\u{5b57}\u{7b26}\u{4e32}" is 测试å—符串
Whew... will prepare the commits and run the testsuites later. :smiley:
Actually I'm considering whether this would need an RFC, as it seems no longer possible to unambiguously reference an argument for count if multiple conflicting formats for that argument are given. Maybe we could do nothing but error on such situations.
@cloudhan
impl Display for Color {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "RGB ({}, {}, {}) 0x{:02X}{:02X}{:02X}", self.red, self.green, self.blue, self.red, self.green, self.blue)
}
}
impl Display for Color {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"RGB ({0}, {1}, {2}) 0x{0:02X}{1:02X}{2:02X}",
self.red, self.green, self.blue
)
}
}
Most helpful comment
Rust by Examples brings me here when I was trying to