Repository: rust
Issue: Tracking issue for improving std::fmt::Arguments and format_args!()
Labels: I-slow,C-tracking-issue,I-heavy,T-libs,A-fmt

Earlier this year in the libs team meeting, I presented several different ideas for alternative implementations of [`std::fmt::Arguments`](https://doc.rust-lang.org/stable/std/fmt/struct.Arguments.html) which could result in smaller binary size or higher performance. Now that #93740 is mostly done, I'll be shifting my focus to fmt::Arguments and exploring those ideas.

Currently, fmt::Arguments is the size of six pointers, and refers to three slices:

- A `&'static [&'static str]` containing the literal parts around the formatting placeholders. E.g. for `"a{}b{}c"`, these are `["a", "b", "c"]`.
- A `&[&(ptr, fn_ptr)]` which is basically a `&[&dyn Display]` (but can point to `Debug` or `Hex` etc. too), pointing to the arguments. This one is not `'static`, as it points to the actual arguments to be formatted.
- A `Option<&'static [FmtArgument]>`, where `FmtArgument` is a struct containing all the options like precision, width, alignment, fill character,  etc. This is unused (`None`) when all placeholders have no options, like in `"{} {}"`, but is used and filled in for all place holders as soon as any placeholder uses any options, like in `"{:.5} {}"`.

Here's a visualisation of that, for a `"a{}b{:.5}c"` format string:

![Diagram](https://user-images.githubusercontent.com/783247/177764666-647da40e-0434-4913-be12-83bffdbfcd45.svg)

An important part of this design is that most of it can be stored in `static` storage, to minimize the amount of work that a function that needs to create/pass a fmt::Arguments needs to do. It can just refer to the static data, and only fill in a slice of the arguments.

Some downsides:
 - A fmt::Arguments is still relatively big (six pointers in size), and not a great type to pass by value. It could be just two pointers in size (one to static data, one to dynamic data), such that it fits in a register pair.
 - It costs quite a lot of static storage for some simple format strings. For example, `"a{}b{}c"` needs a `&["a", "b", "c"]`, which is stored in memory as a (ptr, size) pair referencing three (ptr, size) pairs referencing one byte each, which is a lot of overhead. Small string literals with just a newline or a space are very common in formatting.
 - When even just a single formatting placeholder uses any non-standard options, such as `"{:02x}"`, a relatively large array with all the (mostly default) formatting options is stored for _all_ placeholders.
 - The non-static part that contains the pointers to the arguments contains the pointers to the relevant Display/Debug/etc. implementation as well, even though that second part is constant and could be static. (It's a bit tricky to split those, though.)
 - Even when formatting a simple `&str` argument with a simple `"{}"` placeholder, the full `Display` implementation for `&str` is pulled in, which include code for all the unused options like padding, alignment, etc.

Issues like those are often reason to avoid formatting in some situations, which is a shame.

None of these things are trivial to fix, and all involve a trade off between compile time, code size, runtime performance, and implementation complexity. It's also very tricky to make these tradeoffs for many different use cases at once, as the ways in which formatting is used in a program differs vastly per type of Rust program.

Still, there are many ideas that are worth exploring. It's hard to predict which one will end up being best, so this will involve several different implementations to test and benchmark.

I'll explain the different ideas one by one in the comments below as I explore them.

---

To do:

- [x] Simplify and clean up format_args!() implementation so we can more easily work on it.
    - [x] #100277
    - [x] #100996
    - [x] #101000
    - [x] #106806
    - [x] #101569
    - [x] Change formatting items to lang items (part of #106745)
    - [x] Remove all public exports of the lang items, after the beta 1.69 bump: https://github.com/rust-lang/rust/pull/110616
    - [x] Remove the `V1` suffix from the `ArgumentV1` and `FlagV1` types: https://github.com/rust-lang/rust/pull/110766
- [x] Move format args into the AST, so we can do more with it.
   - [x] MCP: https://github.com/rust-lang/compiler-team/issues/541
   - [x] Move to AST: #106745
     - [x] Explore variation: #106770
       - [x] Accidentally solve [#69455](https://github.com/rust-lang/rust/issues/69455): #106820 
     - [x] Work around #106930
- [x] Remove Clippy's dependence on format_args's implementation details, to allow refactoring format_args without having to fix clippy every time.
   - [x] [Zulip discussion](https://rust-lang.zulipchat.com/#narrow/stream/257328-clippy/topic/format_args/near/321242198)
   - [x] Update lint implementations https://github.com/rust-lang/rust-clippy/issues/10233
      - [x] UselessFormat lint: https://github.com/rust-lang/rust-clippy/pull/10561
      - [x] FormatArgs lints: https://github.com/rust-lang/rust-clippy/pull/10484
      - [x] FormatImpl lints: https://github.com/rust-lang/rust-clippy/pull/10561
      - [x] ManualAssert lint: https://github.com/rust-lang/rust-clippy/pull/10276
      - [x] ExplicitWrite lint: https://github.com/rust-lang/rust-clippy/pull/10561
      - [x] Write lints: https://github.com/rust-lang/rust-clippy/pull/10275
      - [x] expect_fun_call lint: https://github.com/rust-lang/rust-clippy/pull/10561
   - [x] Synchronize the cilppy subtree: https://github.com/rust-lang/rust/pull/110003
- [x] Optimizations for format_args!() within the compiler (independent of std::fmt::Arguments)
   - [x] Inlining and flattening: https://github.com/rust-lang/rust/issues/78356
      - [x] Update API docs to allow this for std::fmt::Arguments::as_str(): #106823
      - [x] Implementation as unstable option: https://github.com/rust-lang/rust/pull/106824
      - [x] Enable it by default: https://github.com/rust-lang/rust/pull/109999
      - [x] Accidentally introduce a bug: https://github.com/rust-lang/rust/issues/110769
      - [x] Fix the bug: https://github.com/rust-lang/rust/pull/110770
- [ ] New fmt::Arguments representation to reduce size and allow conversion from `&str`: https://github.com/rust-lang/rust/pull/115129
- [ ] Reduce amount of (unnecessary) fmt code pulled in
  - [ ] https://github.com/rust-lang/rust/pull/104525
- [ ] Make sure `write!(f, "literal")` is just as efficient as `f.write_str("literal")`
  - [x] https://github.com/rust-lang/rust/pull/121001
- [ ] Experiment with various ideas for changing format_args and fmt::Arguments.
   - [x] Implement and try out the closure idea. #101568
     (Impact on compile time too big, because of all the extra code generation.)
   - [ ] Implement and try out the "encoded formatting instructions" idea.
      - [x] First partial attempt: #84823
      - [ ] A better/complete attempt
   - [ ] ... (more ideas)
- [ ] Implement the winning design
   - [ ] ... (?)