Search code examples
rustformatter

Rust calling fmt function directly


I'm trying to implement different Display formats depending on arguments. Something like print_json() and print_pretty(). I could of course implement it as a function returning string print_json(&self)->String but I'm wondering if it would be possible to have print_json(&self,f: &mut Formatter<'_>) -> std::fmt::Result and print_pretty(&self,f: &mut Formatter<'_>) -> std::fmt::Result instead. Then I could just call either of those functions depending on use case. But how do I obtain instance of Formatter directly? Ideally I would like to do something like

let mut string = String::new();
my_object.print_pretty(&mut string);
return string;

Solution

  • But how do I obtain instance of Formatter directly?

    As far as I know, the only way to get a Formatter is to receive one by implementing Display or one of the other formatting traits.

    I've implemented my own scheme to add more formatting options, based on this core trait:

    /// Objects for which alternate textual representations can be generated.
    /// These are analogous to [`Display`] and [`Debug`], but have additional options.
    pub trait CustomFormat<F: Copy> {
        /// Wrap this value so that when formatted with [`Debug`] or [`Display`] it uses
        /// the given custom format instead.
        fn custom_format(&self, format_type: F) -> CustomFormatWrapper<'_, F, Self> {
            CustomFormatWrapper(format_type, self)
        }
    
        /// Implement this to provide custom formatting for this type.
        fn fmt(&self, fmt: &mut fmt::Formatter<'_>, format_type: F) -> fmt::Result;
    }
    

    Types which wish to support additional formats implement CustomFormat<F> where F is a specific format type or format options; for your use case they could be struct Json; and struct Pretty;. Or, you could have two different traits — I just found that having a general-purpose one reduced code duplication.

    As an implementation example, here I define a custom format for std::time::Duration. It looks just like a Debug or Display implementation except that it takes an extra format-options parameter (which it ignores because StatusText doesn't carry any extra options with it):

    impl CustomFormat<StatusText> for Duration {
        fn fmt(&self, fmt: &mut fmt::Formatter<'_>, _: StatusText) -> fmt::Result {
            write!(fmt, "{:5.2?} ms", (self.as_micros() as f32) / 1000.0)
        }
    }
    

    Your code example using these tools would be:

    let mut string = String::new();
    write!(string, "{}", my_object.custom_format(Pretty)).unwrap();
    return string;
    

    The "{}" format string no longer controls the format; it's just a placeholder to invoke the format mechanism at all, via the CustomFormatWrapper which implements Display (and also Debug) to invoke the custom formatting:

    pub struct CustomFormatWrapper<'a, F: Copy, T: CustomFormat<F> + ?Sized>(F, &'a T);
    
    impl<'a, F: Copy, T: CustomFormat<F>> Debug for CustomFormatWrapper<'a, F, T> {
        fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
            <T as CustomFormat<F>>::fmt(self.1, fmt, self.0)
        }
    }
    
    impl<'a, F: Copy, T: CustomFormat<F>> Display for CustomFormatWrapper<'a, F, T> {
        fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
            <T as CustomFormat<F>>::fmt(self.1, fmt, self.0)
        }
    }
    

    This may be an over-engineered solution for your purposes. The key elements that are needed are the wrapper type which contains a reference to the value to be formatted, and that it some standard formatting trait such as Display to forward to your custom formatting trait (or, if you only ever want to custom-format one type, just the method on that object).

    Link to full source code in context