fast_cat/
lib.rs

1#![doc = include_str!("../Readme.md")]
2
3#[macro_export]
4macro_rules! _concat_str_internal {
5    (
6        @pre {$($pre:stmt,)*},
7        @strname $strname:ident,
8        @len $($len:expr)?, 
9        @appends {$($appends:expr,)*},
10        $head:literal $(, $($tail:tt)*)?
11    ) => {
12        _concat_str_internal!(
13            @pre {$($pre, )*
14                let tmp_res = $head
15            ,},            
16            @strname $strname,
17            @len $($len +)? tmp_res.len(), 
18            @appends {
19                $($appends,)* 
20                $strname.push_str(tmp_res),
21            }, 
22            $($($tail)*)?
23        )
24    };
25
26    (
27        @pre {$($pre:stmt,)*},
28        @strname $strname:ident,
29        @len $($len:expr)?, 
30        @appends {$($appends:expr,)*},
31        $head:ident $(, $($tail:tt)*)?
32    ) => {
33        $crate::_concat_str_internal!(
34            @pre {$($pre, )*},
35            @strname $strname,
36            @len $($len +)? $head.len(), 
37            @appends {
38                $($appends,)* 
39                $strname.push_str($head),
40            }, 
41            $($($tail)*)?
42        )
43    };
44
45    (
46        @pre {$($pre:stmt,)*},
47        @strname $strname:ident,
48        @len $($len:expr)?, 
49        @appends {$($appends:expr,)*},
50        $head:expr $(, $($tail:tt)*)?
51    ) => {
52        $crate::_concat_str_internal!(
53            @pre {$($pre, )*
54                let tmp_res = $head
55            ,},
56            @strname $strname,
57            @len $($len +)? tmp_res.len(), 
58            @appends {
59                $($appends,)* 
60                $strname.push_str(tmp_res),
61            }, 
62            $($($tail)*)?
63        )
64    };
65
66    (
67        @pre {$($pre:stmt,)*},
68        @strname $strname:ident,
69        @len $len:expr,
70        @appends {$($appends:expr,)*},
71    ) => {
72        {
73            $($pre)*
74            let mut $strname = String::with_capacity($len);
75            $($appends;)*
76            $strname
77        }
78    };
79}
80
81/// Concatenates a sequence of string-like arguments into a new `String`.
82///
83/// This macro offers high-performance string concatenation by calculating the total
84/// length of all arguments upfront and performing only a **single memory allocation**.
85/// It serves as a more efficient alternative to repeated use of the `+` operator or
86/// `format!` when all arguments are string-based.
87///
88/// # Arguments
89///
90/// The macro accepts a comma-separated list of arguments that can be converted to `&str`:
91/// - String literals (e.g., `"hello"`).
92/// - `&String` variables (e.g., `&my_string`).
93/// - `&str` slices (e.g., `my_slice`).
94/// - Expressions that evaluate to a string type (e.g., `&user.name` or `&num.to_string()`).
95///
96/// # Panics
97///
98/// The macro itself does not introduce panics. However, expressions passed to it
99/// might panic, for example, due to indexing out of bounds.
100///
101/// # Examples
102///
103/// Basic usage:
104/// ```
105/// # use fast_cat::concat_str; // Замените `fast_cat` на имя вашего крейта
106/// let s = concat_str!("the quick ", "brown ", "fox");
107/// assert_eq!(s, "the quick brown fox");
108/// ```
109///
110/// Mixing literals and variables:
111/// ```
112/// # use fast_cat::concat_str;
113/// let color = "brown";
114/// let animal = String::from("fox");
115/// let sentence = concat_str!("the quick ", color, " ", &animal, " jumps over...");
116/// assert_eq!(sentence, "the quick brown fox jumps over...");
117/// ```
118///
119/// Using expressions:
120/// ```
121/// let num = 42;
122/// let text = concat_str!("The answer is ", &num.to_string(), ".");
123/// assert_eq!(text, "The answer is 42.");
124/// ```
125#[macro_export]
126macro_rules! concat_str {
127    ($($tt:tt)+) => {
128        $crate::_concat_str_internal!(@pre {}, @strname res_string, @len, @appends {}, $($tt)+)
129    };
130}
131
132
133#[cfg(test)]
134mod tests {
135    #[test]
136    fn rtest() {
137        let a = "test";
138        let b = "local".to_string();
139        let c = "focal".to_string();
140        let i = 3;
141
142        assert_eq!(
143            concat_str!("", ""),
144            ""
145        );
146
147        assert_eq!(
148            concat_str!("so, ", a, "test"),
149            "so, testtest"
150        );
151
152        assert_eq!(
153            concat_str!("so, ", &b, " ", &c),
154            "so, local focal"
155        );
156
157        assert_eq!(
158            concat_str!("so, ", &i.to_string(), " ", &c),
159            "so, 3 focal"
160        );
161
162        // recursion limit by default is 128, so it should compile
163        let repeat = "repeat?";
164        let repeat_o = "repeat?".to_string();
165        assert_eq!(
166            concat_str!(
167                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
168                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
169                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
170                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
171                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
172                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
173                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
174                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
175                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
176                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
177                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
178            ),
179            "repeat?".repeat(100)
180        );
181    }
182
183    #[test]
184    fn explicit_failure_test() {
185        let short_str = "1".to_string();
186        let long_str = "long_string".to_string();
187
188        assert_eq!(
189            concat_str!(&short_str, " ", &long_str),
190            "1 long_string"
191        );
192    }
193
194    #[cfg(feature="count-allocations")]
195    #[test]
196    fn test_single_alloc() {
197        let repeat = "repeat?";
198        let repeat_o = "repeat?".to_string();
199        let alloc_count = allocation_counter::measure(|| {
200            let _ = concat_str!(
201                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
202                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
203                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
204                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
205                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
206                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
207                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
208                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
209                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
210                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
211                "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, "repeat?", repeat, &repeat_o, repeat,
212            );
213        });
214        assert_eq!(alloc_count.count_total, 1);
215    }
216}