Repository navigation
format_args!() could 'inline' literal arguments #78356
Description
Activity
- addedC-enhancementCategory: An issue proposing an enhancement or a PR with one.Category: An issue proposing an enhancement or a PR with one.A-fmtArea: `core::fmt`Area: `core::fmt`
on Oct 25, 2020 - addedT-libs-api[DEPRECATED; DO NOT USE][DEPRECATED; DO NOT USE]I-heavyIssue: Problems and improvements with respect to binary size of generated code.Issue: Problems and improvements with respect to binary size of generated code.I-slowIssue: Problems and improvements with respect to performance of generated code.Issue: Problems and improvements with respect to performance of generated code.
on Oct 25, 2020 Do we expect this kind of thing to actually happen right now? It seems like we should if anything just lint against it, and the workaround for the single-arg-panic string could be to do
panic::panic_any("{}")instead ofpanic!("{}", "{}").Do we expect this kind of thing to actually happen right now? It seems like we should if anything just lint against it, and the workaround for the single-arg-panic string could be to do
panic::panic_any("{}")instead ofpanic!("{}", "{}").would that actually work in
#![no_std]? I don't think there's acore::panic::panic_any...Do we expect this kind of thing to actually happen right now?
If you're asking if anyone has time to implement this: Yes, I believe I can find the time for this some time soon.
the workaround for the single-arg-panic string could be to do
panic::panic_any("{}")instead ofpanic!("{}", "{}").panic_anywill only exist instd, notcore. The bloat problem is mostly a problem inno_stdprograms.Reacted by Esteban Kuber and Finn BearIf you're asking if anyone has time to implement this
Sorry, I'm asking if we expect that something like
println!("hello: {}", "world")actually exists in codebases in significant numbers.panic_any will only exist in std, not core. The bloat problem is mostly a problem in no_std programs.
Suggesting
core::panicking::panicwould be the thing then I suppose.I'm asking if we expect that something like
println!("hello: {}", "world")actually exists in codebases in significant numbers.Ah. I think it mostly happens as the result of macro expansion. E.g.
error!("x")expanding toeprintln!("error: {}", "x"). Though, many macros useconcat!()andpanic(thing)directly. E.g.println!("Failure: {}", stringify!(expr))is sometimesprintln!(concat!("Failure: ", stringify!(expr))(which then breaks for expressions with braces). Orpanic!("error: {}", format_args!(fmt, args...))is sometimes writtenpanic!(concat!("error: ", fmt), args...), etc.Suggesting
core::panicking::panicwould be the thing then I suppose.That one requires a
'staticlifetime, so then the suggestion would be different for'staticstrings and non-'staticstrings. Would be nice if it didn't require a special case. (It'd also require that function to become stable.)
Anyway, I opened this issue just as a reminder to investigate the possibility, not because I think we should definitely do this. There are good ways to avoid the need for this like you mention (
core::panicking::panic), but it might be nice if no special cases were necessary while implementing macros likeassert!(), and"{}", ".."was just as efficient as any other solution.Reacted by tesuji and Esteban KuberMy observations have consistently suggested that people use
format!orwrite!for seemingly "trivial" reasons All The Time because when you start withprintln!it's what feels like the most natural and easiest way. And then they do wrap it in other macros, yes, so it is best to try to take the effort to reduce the amount of overhead going on here.Reacted by Mara BosHey @rust-lang/wg-const-eval, I'd like to hear your thoughts about the feasibility of this feature.
Some examples of things it could do:
format_args!("a {}", "b")→format_args!("a b"): Implementing this does not need constant evaluation. Theformat_argsmacro itself can directly see the literal and inline it.format_args!("a {}", SOME_STR)→format_args!("a b"): The macro can't see the contents or type of the constant (or even see that it is a constant), so this will need to happen at some other point. How could this work?format_args!("a {}", 123)→format_args!("a 123"): The macro could do this, but will have to make assumptions about how integers are formatted. Can work for simple cases. More advanced cases would need constant evaluation of theDisplaytrait, but I'm guessing that's still far away.format_args!("a {}", SOME_INT)→format_args!("a 123"): Just like 2, requires constant evaluation after the macro is already expanded, and like 3 requires knowledge about how to format integers.format_args!("a {}", format_args!("b"))→format_args!("a b"): The macro could do this directly if it can know that theformat_argstoken actually refers to itself. (I think it can, since it can also expandconcat!()etc.)format_args!("a {}", format_args!("b {}", c))→format_args!("a b {}", c): Same as the previous one, but needs more complicated logic.
For const_panic specifically, 2 would be very useful to have
panic!("{}", MSG)work (to replacepanic!(MSG)which won't work in Rust 2021). But 4 could also be very useful to give more useful panic messages during constant evaluation.Many of these I could implement by modifying how the
format_argsbuiltin macro works, but that won't work for the cases where it refers to constants instead of only using literals, which are probably the most useful ones for const_panic.So my question is: Can that be implemented without making things extremely complicated? Recognizing and reversing+rebuilding the
fmt::Arguments::newcall that results from theformat_args!()invocation when processing the mir sounds complicated to me. Is there an easier way? Could we add special metadata to make it easier to process at a later stage? Could we add a new expression type specifically for 'format args' such that it doesn't appear as a big complicated call expression tofmt::Arguments::new, but simply as a 'format args expression' that gets substituted afmt::Argumentsconstructor call much later? Or any other ideas?Reacted by Joe Richey, djugei, Jakub Beránek and Evgeniy DushistovThe problem isn't format_args at all. We can make new_v1 const fn today, even new_v1_formatted. The problem is panic_fmt which wants to format the Arguments thing properly. Since at this point the integers or strings to be formatted are only available via
as part ofrust/library/core/src/fmt/mod.rs
Lines 261 to 264 in 6f69192
pub struct ArgumentV1<'a> { value: &'a Opaque, formatter: fn(&Opaque, &mut Formatter<'_>) -> Result, } , we can't really do anything at CTFE time, unless we start adding some more lang items. Most notably we'd need to make ::fmt a lang item, so we can check whether the pointer in ArgumentsV1 is a pointer to that function, and if it is, print that string to our compile-time-panic.rust/library/core/src/fmt/mod.rs
Lines 408 to 418 in 6f69192
pub struct Arguments<'a> { // Format string pieces to print. pieces: &'a [&'static str], // Placeholder specs, or `None` if all specs are default (as in "{}{}"). fmt: Option<&'a [rt::v1::Argument]>, // Dynamic arguments for interpolation, to be interleaved with string // pieces. (Every argument is preceded by a string piece.) args: &'a [ArgumentV1<'a>], } This scheme does not scale to anything but panicking, because if the formatting arg is not a string (or another potentially hardcoded type like integers), then we have to abort with an error. Since we are already panicking, we can just continue and print something like "{unprintable}".
I'd rather not add support for integers -- if we go down that route we will end up with a (hacky) const-side reimplementation of a subset of the formatting machinery. As much as possible, we should use Rust code for this, not special hacks in the interpreter engine.
Given that the CTFE core engine already supports the entire display machinery (demonstrated by the fact that it works in Miri), we might alternatively poke a small hole into the const checks that allows executing formatting machinery from
panic_fmtonly -- something like "miri unleashed", but more targeted.Reacted by Oli SchererReacted by Oli SchererReacted by Oli Scherer9 remaining items
You can't call
format_args!. Not even when you only have the format string. (format_args!("a")does not work)format_args!("a")does not workThat's on purpose, to avoid making that part of the stability guarantees for now. We have an unstable/internal
const_format_args!("a")that does work, which is used bypanic!("a").some time Soon™.
2½ years later, I finally got around to implementing it: #106824
Reacted by Mateusz Mikuła and Finn BearReacted by Diana, Vrishabh and Jakub BeránekReacted by Diana, Clar Fon, Jakub Beránek and Finn BearOpened #106823 to change the documentation of
fmt::Arguments::as_str()to allow for inlined/flattened format arguments.Would inlining literal arguments allow
format_argsto be used in const contexts: #108595?The inlining logic could play some role if we decide to allow format_args in const, but whether to allow format_args in const is an entirely separate discussion and decision.
Reacted by Zyad Hassan and Clar FonThis is now available on nightly with
-Zflatten-format-args=yes.Reacted by Evgeniy Dushistov and Finn BearThis is now implemented and enabled by default in Rust 1.71.0: #109999
Reacted by Mateusz Mikuła, Michael Velbaum, Jakub Beránek, Kisaragi and bb010g- added a commit that references this issue
on Jun 1, 2023 - added a commit that references this issue
on Apr 16, 2024
Arguments::as_strallows code to handle argument-lessfmt::Argumentslikeformat_args!("literal")specially by not pulling in any formatting code.We should investigate if we can improve
format_args!("hello: {}", "literal")to result in the same trivialfmt::Argumentsasformat_args!("hello: literal"), such that any.as_str()-based optimizations are also available for it.That could also make things like
panic!("{}", "message");work withconst_panic, removing the need forpanic!(CONST_STR).This makes
panic!("hello")exactly as cheap (and const) aspanic!("{}", "hello"), which is also important for #78088 to make sure there are no downsides to its suggestions.It'd also reduce the need for using
concat!(..), since it'd no longer be less efficient to add a{}placeholder to concat literals in a formatting string instead.As a bonus:
format_args!("a: {}", format_args!("b: {}", ..))could maybe also improved to produce the same asformat_args!("a: b: {}", ..).Assigning to myself to investigate some time Soon™.