Skip to content

Tracking Issue for FormattingOptions #118117

Open

Description

@EliasHolzmann

Feature gate: #![feature(formatting_options)]

This is a tracking issue for fmt::FormattingOptions.

FormattingOptions can be used to construct custom Formatters at runtime.

Public API

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct FormattingOptions { … }

enum Sign {
    Plus, 
    Minus
}

impl FormattingOptions {
    pub fn new() -> Self;

    pub fn sign(&mut self, sign: Option<Sign>) -> &mut Self;
    pub fn zero_pad(&mut self, zero_pad: bool) -> &mut Self;
    pub fn alternate(&mut self, alternate: bool) -> &mut Self;
    pub fn fill(&mut self, fill: Option<char>) -> &mut Self;
    pub fn alignment(&mut self, alignment: Option<Alignment>) -> &mut Self;
    pub fn width(&mut self, width: Option<usize>) -> &mut Self;
    pub fn precision(&mut self, precision: Option<usize>) -> &mut Self;

    pub fn get_sign(&self) -> Option<Sign>;
    pub fn get_zero_pad(&self) -> bool;
    pub fn get_alternate(&self) -> bool;
    pub fn get_fill(&self) -> Option<char>;
    pub fn get_alignment(&self) -> Option<Alignment>;
    pub fn get_width(&self) -> Option<usize>;
    pub fn get_precision(&self) -> Option<usize>;
    
    pub fn create_formatter<'a>(self, write: &'a mut (dyn Write + 'a)) -> Formatter<'a>;
}

impl<'a> Formatter<'a> {
    pub fn new(write: &'a mut (dyn Write + 'a), options: FormattingOptions) -> Self;
    pub fn with_options(&mut self, options: FormattingOptions) -> Self;
    
    pub fn sign(&self) -> Option<Sign>;

    pub fn options(&self) -> FormattingOptions;
}

Steps / History

Unresolved Questions

  • Should this expose the "debug as hex" flags? ({:x?} and {:X?})

Footnotes

  1. https://std-dev-guide.rust-lang.org/feature-lifecycle/api-change-proposals.html ↩

  2. https://std-dev-guide.rust-lang.org/feature-lifecycle/stabilization.html ↩

Activity

  1. added
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    T-libs-api[DEPRECATED; DO NOT USE]
    on Nov 21, 2023
  2. EliasHolzmann commented on Nov 21, 2023

    @EliasHolzmann
    ContributorAuthor

    @rustbot claim

  3. changed the title [-]Tracking Issue for XXX[/-] [+]Tracking Issue for `FormattingOptions`[/+] on Nov 21, 2023
  4. added 2 commits that reference this issue on Feb 15, 2024
  5. added 2 commits that reference this issue on Dec 5, 2024
  6. joboet commented on Jan 22, 2025

    @joboet
    Member

    I was wondering: could the signature for Formatter::new be changed to take a reference to the FormattingOptions instead of passing them by value? That way, LLVM wouldn't need to copy around the options as often, which seems like it could improve performance and binary size in cases where the same options are used multiple times, for instance when using the default options.

  7. added a commit that references this issue on Jan 24, 2025
  8. added a commit that references this issue on Jan 25, 2025
  9. added a commit that references this issue on Jan 25, 2025
  10. EliasHolzmann commented on Jan 25, 2025

    @EliasHolzmann
    ContributorAuthor

    @joboet I don't think so. Formatter directly holds a FormattingOptions internally: https://doc.rust-lang.org/nightly/src/core/fmt/mod.rs.html#524

    If Formatter::new takes its FormattingOptions by reference instead of by value, we'd have to copy the options in that function anyway. (Instead, you probably could have the contents of FormattingOptions behind a Rc so that copying/cloning is cheaper, which might indeed improve performance for use cases with heavy FormattingOptions reuse. However, I don't think that this will compensate the ref-counting overhead that is in most use cases probably unnecessary.)

    In most cases, when the FormattingOptions can be used multiple times, the target buffer of the resulting Formatter is probably also the same – in this case, you can just use the Formatter you construct.

  11. added a commit that references this issue on Feb 11, 2025
  12. 6 remaining items

  13. TechPizzaDev commented on Aug 26, 2025

    @TechPizzaDev

    FormattingOptions was 3x pointer size last I checked, which is why I found the Copy derive strange.

    There was discussion to pack it down, but that would be annoying if any new fields were to be added in the future.

  14. added a commit that references this issue on Aug 26, 2025
  15. added a commit that references this issue on Aug 26, 2025
  16. added 2 commits that reference this issue on Sep 9, 2025
  17. theemathas commented on Feb 13, 2026

    @theemathas
    Contributor

    I and @SabrinaJewson found that, with Formatter::new(), it's possible to substitute a different Formatter in a Debug impl, causing the rest of the surrounding Debug impl write to a different place. For example:

    #![feature(formatting_options)]
    
    use std::fmt::*;
    
    struct Replacer;
    impl Debug for Replacer {
        fn fmt(&self, f: &mut Formatter<'_>) -> Result {
            *f = Formatter::new(Box::leak(Box::new(PrintToStdout)), FormattingOptions::default());
            Ok(())
        }
    }
    
    struct PrintToStdout;
    impl Write for PrintToStdout {
        fn write_str(&mut self, s: &str) -> Result {
            print!("{s}");
            Ok(())
        }
    }
    
    #[expect(dead_code)]
    #[derive(Debug)]
    struct Thing {
        x: Replacer,
        y: &'static str,
    }
    
    fn main() {
        let _ = format!("{:?}", Thing { x: Replacer, y: "hi hi hello\n" });
    }

    Output:

    , y: "hi hi hello\n" }
    

    Also, changing the {:?} to {:#?} causes the program to output a single comma:

    ,
    
  18. jhpratt commented on Feb 13, 2026

    @jhpratt
    Member

    Short of changing the lifetime to be invariant, which would probably be a breaking change(?), I'm not sure what could be done about that. Assuming we want to keep Formatter::new, of course.

  19. SabrinaJewson commented on Feb 13, 2026

    @SabrinaJewson
    Contributor

    The lifetime is already invariant, so that wouldn’t help anything – the only solution would be to disallow constructing Formatters entirely. But this seems highly unlikely to break anything unless the user is really trying to break it, and it doesn’t seem to produce unsoundness either like &mut ReadBuf did. If we really did need to fix it:

    impl Formatter<'_> {
        pub fn with<R>(
            write: &mut dyn Write,
            options: FormattingOptions,
            f: impl FnOnce(&mut Formatter<'_>) -> R,
        ) -> R;
    }
  20. EliasHolzmann commented on Feb 13, 2026

    @EliasHolzmann
    ContributorAuthor

    This is indeed an interesting edge case, thanks for pointing that out, I didn't think about that.

    In theory, if you e.g. debug output some type from a sketchy dependency that does funny business, that could redirect your output to somewhere else, which might take you a while to figure out. But if you run untrusted code, you've got bigger problems than that. All in all, I have to agree with Sabrina that this doesn't really break anything in practice. And while I'm not familiar with the problems around &mut ReadBuf, there is no unsafe in FormattingOptions, so I don't see how it coild lead to unsoundness (assuming the soundness of safe Rust, of course).

  21. vitu168 commented on May 14, 2026

    @vitu168
  22. added
    T-libsRelevant to the library team, which will review and decide on the PR/issue.
    and removed
    T-libs-api[DEPRECATED; DO NOT USE]
    on Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCT-libsRelevant to the library team, which will review and decide on the PR/issue.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions