Skip to main content

rama_utils/
fmt.rs

1//! Allocation-aware formatting helpers.
2
3use core::fmt::{self, Write as _};
4
5use crate::std::string::String;
6
7/// Format `args` into a new string with the requested initial `capacity`.
8///
9/// This is useful when the caller can cheaply determine the rendered length
10/// from its inputs. Formatting happens once and the string does not reallocate
11/// as long as `capacity` is large enough.
12///
13/// # Panics
14///
15/// Panics if a formatting trait implementation supplied through `args` returns
16/// [`fmt::Error`], matching [`format!`].
17#[must_use]
18#[expect(
19    clippy::expect_used,
20    reason = "match format! semantics for caller-provided formatting implementations"
21)]
22pub fn format_with_capacity(capacity: usize, args: fmt::Arguments<'_>) -> String {
23    let mut output = String::with_capacity(capacity);
24    output
25        .write_fmt(args)
26        .expect("formatting into a String cannot fail");
27    output
28}
29
30/// Clear `output`, try to format `args` into it, and return the resulting string.
31///
32/// The allocation owned by `output` is retained, making this suitable for a
33/// scratch string reused across a loop or a sequence of writes.
34///
35/// If formatting fails, `output` contains the successfully formatted prefix.
36pub fn try_format_into<'a>(
37    output: &'a mut String,
38    args: fmt::Arguments<'_>,
39) -> Result<&'a str, fmt::Error> {
40    output.clear();
41    output.write_fmt(args)?;
42    Ok(output.as_str())
43}
44
45/// Build a deferred [`Display`](fmt::Display) value from a formatting closure.
46///
47/// This is useful for passing composed output directly to another formatter
48/// without first allocating an intermediate string.
49pub fn display_fn<F>(formatter: F) -> DisplayFn<F> {
50    DisplayFn(formatter)
51}
52
53/// Deferred formatting value created by [`display_fn`].
54#[derive(Clone, Copy)]
55pub struct DisplayFn<F>(F);
56
57impl<F> fmt::Display for DisplayFn<F>
58where
59    F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
60{
61    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
62        (self.0)(formatter)
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use core::fmt;
69
70    use super::*;
71
72    #[test]
73    fn formats_with_requested_capacity() {
74        let output = format_with_capacity(32, format_args!("hello {value}", value = 42));
75
76        assert_eq!(output, "hello 42");
77        assert!(output.capacity() >= 32);
78    }
79
80    #[test]
81    fn formats_into_reused_string() {
82        let mut output = String::with_capacity(64);
83        output.push_str("old contents");
84        let allocation = output.as_ptr();
85
86        let formatted =
87            try_format_into(&mut output, format_args!("new {value}", value = "contents")).unwrap();
88
89        assert_eq!(formatted, "new contents");
90        assert_eq!(output.as_ptr(), allocation);
91    }
92
93    #[test]
94    fn formatting_into_reused_string_propagates_display_errors() {
95        struct Fails;
96
97        impl fmt::Display for Fails {
98            fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
99                Err(fmt::Error)
100            }
101        }
102
103        let mut output = String::from("old contents");
104        let value = Fails;
105        let error = try_format_into(&mut output, format_args!("prefix {value}")).unwrap_err();
106
107        assert_eq!(error, fmt::Error);
108        assert_eq!(output, "prefix ");
109    }
110
111    #[test]
112    fn display_fn_defers_display_formatting() {
113        let value =
114            display_fn(|formatter: &mut fmt::Formatter<'_>| formatter.write_str("deferred"));
115
116        assert_eq!(value.to_string(), "deferred");
117    }
118}