-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Specialize format! for simple "{}" case #76531
Conversation
If the first argument to format! is "{}" then the rest of the args must be something that implements fmt::Display and thus ToString. We can thus call ToString on it. In order to type-safely eat all those arguments, a struct wrapper inside a closure prevents misuse.
(rust_highfive has picked a reviewer for you, use r? to override) |
Initial experiments with measureme's toolset suggest mixed results due to more effort in expanding this macro, but because the resulting code is often more familiar to Rust's existing patterns, even with this bespoke type guard, it can sometimes optimize better (or at least with less effort), but I don't have things set up to run any comprehensive/conclusive experiments yet. |
Why not put For example, the rust/library/std/src/macros.rs Line 10 in d92155b
rust/library/std/src/panicking.rs Line 424 in d92155b
|
This uses #[unstable] and #[allow_internal_unstable] to reduce the need for expansion of the struct in every macro invocation.
Ah, I am not as familiar with that sort of thing yet! It seems like a good idea. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am personally unsure that we want to do this. I think clippy already lints on format!("{}", foo)
to suggest foo.to_string()
.
I would prefer that we at least move the fast closure to an associated function on NeedsDisplay, not a closure, to avoid generating more code than is necessary.
let fast = |t: NeedsDisplay<_> | $crate::string::ToString::to_string(t.inner); | ||
// This TokenTree must resolve to a Displayable value to be valid. | ||
fast(NeedsDisplay { inner: &{$($arg)*} }) | ||
}}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will want a codegen test I think for this, at least -- it looks like this is just not triggering to me, because I would expect NeedsDisplay
to not resolve here. Generally we need the type to be public in order for it to be constructible outside the current crate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
codegen – tests that compile and then test the generated LLVM code to make sure that the optimizations we want are taking effect. See LLVM docs for how to write such tests.
Ah, one of these? Well, that just made this PR more Exciting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that before working on that I would really fix this macro to make sure this arm is getting expanded, because right now that seems pretty unlikely to me. See also the comment below about needing :ident on the arm, most likely, which'll further simplify this branch.
library/alloc/src/macros.rs
Outdated
#[macro_export] | ||
#[stable(feature = "rust1", since = "1.0.0")] | ||
macro_rules! format { | ||
// A faster path for simple format! calls. | ||
("{}", $($arg:tt,?)+) => {{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: does this need to deal with a full $($arg:tt,?)+
? Maybe it could just take $e:expr ,?
and let all the more complicated cases still be handled by the other arms of the macro.
(I don't think there's a need to optimize format!("{}", 1, 2, 3, 4, 5, 6)
.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's worse than that: even the expr version will not be correct.
Right now, format!
parses named arguments, like a = 1
. If that was parsed as an expression, then it would evaluate to ()
, and this would not pass the assertion.
let a = format!("{}", x = 1);
assert_eq!(&a, "1");
I think it actually needs to be $arg:ident
, and the optimization probably wouldn't be triggered very often as a result. That's annoying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wow, I didn't think about that kind of usage.
@notriddle Can you post or link me to some more... uh... weird examples like that? I mean, it might make me actually just close this PR but I'd like to have an opportunity to fool around with the alternate cases to see how I can handle it in the pattern matching and if it destroys the gains.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I'm aware of the usual usages, but that doesn't include any examples that I can see which match the format!("{}", x = 1);
case that I can see, I've never seen a named parameter then being passed to an "anonymous" formatting parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but that doesn't include any examples that I can see which match the
format!("{}", x = 1);
case that I can see
That's because it's a bug and shouldn't be accepted: #45256
@workingjubilee any updates? |
ceee28d
to
1ee0392
Compare
I can't believe that worked. |
Triage: Seems ready for review. |
r? @m-ou-se who has some relevant recent context, I think |
You'll have to do the same async fn f(_: String) {}
fn g() -> impl Send {
async move {
let x = 123;
f(format!("{}", x)).await;
}
}
See #64477 (comment). You can make it work for all expressions instead of only identifiers by making a separate case to handle macro_rules! format {
("{}", $name:ident = $arg:expr $(,)?) => {{
let res = $crate::fmt::Display::to_string(&$arg);
res
}};
("{}", $arg:expr $(,)?) => {{
let res = $crate::fmt::Display::to_string(&$arg);
res
}};
...
} (I also added To make sure any future lint like suggested in #45256 (comment) would still warn about the first case, you could add a |
It'd be good to add some tests. Specifically to:
|
I don't think
|
Ping from triage: |
Aye aye, capitán. |
@workingjubilee Ping from triage, any updates on this? If there's still questions welcome to talk about it over on official Zulip! Thanks. |
@workingjubilee any updates? |
My sincere apologies, shortly after my last post the very computer I sent it from broke. I now have surplus compute again and I would like to try once more to take a look at this, though if another strike of unluck hits me I will try to reply in a more timely fashion and quit the field, at least on this PR, rather than leaving people waiting any further (and leaving myself open to more lightning bolts from Olympus). |
@workingjubilee Triage: what's the current status? |
@@ -102,6 +102,10 @@ macro_rules! vec { | |||
#[macro_export] | |||
#[stable(feature = "rust1", since = "1.0.0")] | |||
macro_rules! format { | |||
// A faster path for simple format! calls. | |||
("{}", $arg:ident) => {{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to make this so restrictive? I think $arg:expr $(,)?
would catch a lot more and still allow calling to_string
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's worse than that: even the expr version will not be correct.
Right now, format!
parses named arguments, like a = 1
. If that was parsed as an expression, then it would evaluate to ()
, and this would not pass the assertion.
let a = format!("{}", x = 1);
assert_eq!(&a, "1");
I think it actually needs to be $arg:ident
, and the optimization probably wouldn't be triggered very often as a result. That's annoying.
Closing this due to inactivity. |
If the first argument to format! is "{}" then the rest of the args must
be something that implements fmt::Display and thus ToString. We can thus
call ToString on it. In order to type-safely eat all those arguments,
a struct wrapper inside a closure prevents misuse.
This fixes #52804.