Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

format! should support multiple formats for one argument #9456

Closed
chris-morgan opened this issue Sep 24, 2013 · 12 comments
Closed

format! should support multiple formats for one argument #9456

chris-morgan opened this issue Sep 24, 2013 · 12 comments
Labels
A-syntaxext Area: Syntax extensions

Comments

@chris-morgan
Copy link
Member

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.

@alexcrichton
Copy link
Member

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.

@alexcrichton
Copy link
Member

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"

@ghost ghost assigned alexcrichton Sep 26, 2013
alexcrichton added a commit to alexcrichton/rust that referenced this issue Sep 27, 2013
As mentioned in rust-lang#9456, the format! syntax extension would previously consider an
empty format as a 'Unknown' format which could then also get coerced into a
different style of format on another argument.

This is unusual behavior because `{}` is a very common format and if you have
`{0} {0:?}` you wouldn't expect them both to be coereced to the `Poly`
formatter. This commit removes this coercion, but still retains the requirement
that each argument has exactly one format specified for it (an empty format now
counts as well).

Perhaps at a later date we can add support for multiple formats of one argument,
but this puts us in at least a backwards-compatible situation if we decide to do
that.
bors added a commit that referenced this issue Sep 27, 2013
As mentioned in #9456, the format! syntax extension would previously consider an
empty format as a 'Unknown' format which could then also get coerced into a
different style of format on another argument.

This is unusual behavior because `{}` is a very common format and if you have
`{0} {0:?}` you wouldn't expect them both to be coereced to the `Poly`
formatter. This commit removes this coercion, but still retains the requirement
that each argument has exactly one format specified for it (an empty format now
counts as well).

Perhaps at a later date we can add support for multiple formats of one argument,
but this puts us in at least a backwards-compatible situation if we decide to do
that.
@steveklabnik
Copy link
Member

Triage: all the same today.

@alexcrichton alexcrichton removed their assignment Apr 2, 2015
@cloudhan
Copy link

cloudhan commented May 9, 2016

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

@xen0n
Copy link
Contributor

xen0n commented May 9, 2016

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!

@xen0n
Copy link
Contributor

xen0n commented May 12, 2016

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?

@hexsel
Copy link

hexsel commented May 12, 2016

Wow, I didn't know about format!("{var_name}", var_name = xxx)!

Is this documented anywhere?

@cloudhan
Copy link

@xen0n
Copy link
Contributor

xen0n commented May 14, 2016

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. 😃

@xen0n
Copy link
Contributor

xen0n commented May 14, 2016

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.

bors added a commit that referenced this issue Jul 13, 2016
Ergonomic format_args!

Fixes #9456 (at last).

Not a ground-up rewrite of the existing machinery, but more like an added intermediary layer between macro arguments and format placeholders. This is now implementing Rust RFC 1618!
@zlx
Copy link

zlx commented Aug 24, 2016

@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)
    }
}

@Vesnica
Copy link

Vesnica commented Nov 20, 2019

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

djkoloski pushed a commit to djkoloski/rust that referenced this issue Sep 21, 2022
Fix `unused_peekable` closure and `f(&mut peekable)` false positives

changelog: Fix [`unused_peekable`] false positive when peeked in a closure or called as `f(&mut peekable)`

The `return`/`break` changes aren't part of the fix, they allow an earlier return in some cases. `break` is replaced with `return` for style purposes as they do the same thing in this case

Fixes rust-lang#9456
Fixes rust-lang#9462
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-syntaxext Area: Syntax extensions
Projects
None yet
Development

No branches or pull requests

8 participants