diskann_utils/lazystring.rs
1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6use std::fmt::{Debug, Display, Formatter, Result};
7
8/// A macro that behaves like `format!` but constructs a [`LazyString`] to defer string
9/// formatting until the result is actually displayed. If the [`LazyString`] is never
10/// displayed, this construct has minimal overhead.
11///
12/// ```rust
13/// use diskann_utils::lazy_format;
14///
15/// let a: f32 = 10.5;
16/// let b: usize = 20;
17///
18/// let lazy_string = lazy_format!("This is a test. A = {}, B = {}", a, b);
19/// assert_eq!(lazy_string.to_string(), "This is a test. A = 10.5, B = 20");
20///
21/// // Formatting of captured members is deferred until the created `LazyString` is formatted.
22/// #[derive(Default)]
23/// struct Formatted(std::cell::Cell<bool>);
24///
25/// impl Formatted {
26/// fn was_formatted(&self) -> bool {
27/// self.0.get()
28/// }
29///
30/// fn mark_as_formatted(&self) {
31/// self.0.set(true)
32/// }
33/// }
34///
35/// impl std::fmt::Display for Formatted {
36/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37/// if self.was_formatted() {
38/// f.write_str("yes")
39/// } else {
40/// self.mark_as_formatted();
41/// f.write_str("not yet")
42/// }
43/// }
44/// }
45///
46/// let f = Formatted::default();
47/// let lazy = lazy_format!("Was this formatted: {f}");
48///
49/// assert!(!f.was_formatted(), "string formatting should be deferred");
50/// assert_eq!(lazy.to_string(), "Was this formatted: not yet");
51///
52/// assert!(f.was_formatted());
53/// assert_eq!(lazy.to_string(), "Was this formatted: yes");
54/// ```
55///
56/// # Creating lazily formatted `'static` error messages
57///
58/// The default [`LazyString`] created by this macro borrows from its formatted arguments
59/// and thus has a lifetime constrained to its arguments.
60///
61/// If a lazily formatted `'static` compliant variation is needed, the "move" variant
62/// can be used (assuming all captured arguments are `'static`):
63///
64/// ```rust
65/// use diskann_utils::lazy_format;
66///
67/// fn assert_static<T: 'static>(_: &T) {}
68///
69/// let x = 10;
70///
71/// let lazy = lazy_format!(move, "x = {x}");
72/// assert_static(&lazy);
73/// assert_eq!(lazy.to_string(), "x = 10");
74/// ```
75#[macro_export]
76macro_rules! lazy_format {
77 (move, $($arg:tt)*) => {
78 $crate::LazyString::new(move |f: &mut std::fmt::Formatter<'_>| {
79 ::core::write!(f, $($arg)*)
80 })
81 };
82 ($($arg:tt)*) => {
83 $crate::LazyString::new(|f: &mut std::fmt::Formatter<'_>| {
84 ::core::write!(f, $($arg)*)
85 })
86 };
87}
88
89/// A struct used to lazily defer string formatting until needed. This is used to implement
90/// [`lazy_format!`]: a lazy version of the standard `format!` macro.
91///
92/// See [`lazy_format!`] for usage.
93pub struct LazyString<F>(F)
94where
95 F: Fn(&mut Formatter<'_>) -> Result;
96
97impl<F> LazyString<F>
98where
99 F: Fn(&mut Formatter<'_>) -> Result,
100{
101 /// Construct a new `LazyString` around the provided lambda.
102 #[doc(hidden)]
103 pub fn new(f: F) -> Self {
104 Self(f)
105 }
106}
107
108impl<F> Display for LazyString<F>
109where
110 F: Fn(&mut Formatter<'_>) -> Result,
111{
112 #[inline]
113 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
114 (self.0)(f)
115 }
116}
117
118impl<F> Debug for LazyString<F>
119where
120 F: Fn(&mut Formatter<'_>) -> Result,
121{
122 #[inline]
123 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
124 f.debug_tuple("LazyString")
125 .field(&format_args!("{self}"))
126 .finish()
127 }
128}
129
130///////////
131// Tests //
132///////////
133
134#[cfg(test)]
135mod test {
136 use super::*;
137
138 fn assert_static<T: 'static>(_: &T) {}
139
140 #[test]
141 fn test_lazy_string() {
142 let x: f32 = 10.5;
143 let y: usize = 20;
144
145 let lazy = LazyString::new(|f: &mut std::fmt::Formatter| {
146 write!(f, "Lazy Message: x = {x}, y = {y}")
147 });
148 assert_eq!(lazy.to_string(), "Lazy Message: x = 10.5, y = 20");
149
150 let lazy = lazy_format!("Lazy Message: x = {x}, y = {y}");
151 assert_eq!(lazy.to_string(), "Lazy Message: x = 10.5, y = 20");
152
153 let lazy = lazy_format!(move, "Lazy Message: x = {}, y = {y}", x);
154 assert_static(&lazy);
155 assert_eq!(lazy.to_string(), "Lazy Message: x = 10.5, y = 20");
156
157 // Verify that `Debug` at least runs.
158 assert_eq!(
159 format!("{:?}", lazy),
160 "LazyString(Lazy Message: x = 10.5, y = 20)",
161 );
162 }
163}