fast_cat/
lib.rs

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