str_cat/lib.rs
1//! This crate provides macros to efficiently concatenate strings without extra
2//! side-effect.
3//!
4//! # Usage
5//! ## Basic usage
6//! ```
7//! use str_cat::str_cat;
8//!
9//! let s = str_cat!("Hello", " ", "World", "!");
10//! assert_eq!(s, "Hello World!");
11//! ```
12//!
13//! which is roughly equivalent to
14//!
15//! ```
16//! # let world = "World".to_owned();
17//! let mut s = String::with_capacity("Hello".len() + " ".len() + "World".len() + "!".len());
18//! s.push_str("Hello");
19//! s.push_str(" ");
20//! s.push_str("World");
21//! s.push_str("!");
22//! ```
23//!
24//! ## No extra side-effect
25//! The macro runs without extra side-effect, which means all involved
26//! expressions are evaluated exactly once.
27//!
28//! ```
29//! # use str_cat::str_cat;
30//! let mut get_world_calls = 0;
31//! let mut get_world = || {
32//! get_world_calls += 1;
33//! "World"
34//! };
35//! let s = str_cat!("Hello", " ", get_world(), "!");
36//! assert_eq!(s, "Hello World!");
37//! assert_eq!(get_world_calls, 1);
38//! ```
39//!
40//! which is roughly equivalent to
41//!
42//! ```
43//! # let get_world = || "World";
44//! let world = get_world(); // evaluate the expression and store it for later use
45//! let mut s = String::with_capacity("Hello".len() + " ".len() + world.len() + "!".len());
46//! s.push_str("Hello");
47//! s.push_str(" ");
48//! s.push_str(&world);
49//! s.push_str("!");
50//! ```
51//!
52//! ## Append to an existing string
53//! ```
54//! # use str_cat::str_cat;
55//! let mut s = "Hello".to_owned();
56//! str_cat!(&mut s; " ", "World!");
57//! assert_eq!(s, "Hello World!");
58//! ```
59//!
60//! ## Reuse existing allocation
61//! ```
62//! # use str_cat::str_cat;
63//! let mut s = "Hello World!".to_owned();
64//! let ptr = s.as_ptr();
65//! let cap = s.capacity();
66//!
67//! s.clear();
68//! str_cat!(&mut s; "Hello");
69//! assert_eq!(s, "Hello");
70//! // Did not grow
71//! assert_eq!(s.as_ptr(), ptr);
72//! assert_eq!(s.capacity(), cap);
73//! ```
74//!
75//! ## Custom minimum capacity
76//! ```
77//! # use str_cat::str_cat;
78//! let s = str_cat!(String::with_capacity(16); "foo", "bar");
79//! assert_eq!(s, "foobar");
80//! assert!(s.capacity() >= 16);
81//! ```
82//!
83//! ## Argument types
84//! Works with any expressions that can dereference to [`str`](str) when
85//! evaluated. Although it should be more simple and efficient to use
86//! [`format!`](format) instead when you can't avoid explicit `.to_string()`
87//! calls.
88//!
89//! ```
90//! # use str_cat::str_cat;
91//! // Just an example. It's better to use `format!` in this case.
92//! let s = str_cat!(
93//! "Hello".to_owned(),
94//! Box::new(" "),
95//! ['W', 'o', 'r', 'l', 'd'].iter().collect::<String>(),
96//! '!'.to_string(),
97//! 123456.to_string(),
98//! );
99//! assert_eq!(s, "Hello World!123456");
100//! ```
101//!
102//! ## Implicit borrowing
103//! Just like [`format!`](format) and [`println!`](println),
104//! [`str_cat`](str_cat) automatically borrows the arguments for you, so you
105//! don't have to type that `&` yourself.
106//!
107//! ```
108//! # use str_cat::str_cat;
109//! let world = "World!".to_owned();
110//! let s = str_cat!("Hello ", world); // no need for `&`
111//! assert_eq!(s, "Hello World!");
112//! assert_eq!(world, "World!"); // not moved, still valid
113//! ```
114//!
115//! ## Variants
116//! There are also variants for [`PathBuf`](std::path::PathBuf),
117//! [`OsString`](std::ffi::OsString) and [`Vec`](Vec).
118//!
119//! ```
120//! use str_cat::os_str_cat;
121//! # use std::ffi::OsStr;
122//! # use std::path::Path;
123//!
124//! // Works for anything that implements AsRef<OsStr>.
125//! let s = os_str_cat!(
126//! OsStr::new("Hello"),
127//! OsStr::new(" ").to_owned(),
128//! Path::new("World"),
129//! "!",
130//! );
131//! assert_eq!(s, OsStr::new("Hello World!"));
132//! ```
133
134/// Concatenate strings for a [`String`](String).
135///
136/// It requires all elements to be able to dereference to [`str`](str) (impl [`Deref<Target = str>`](std::ops::Deref)).
137///
138/// # Example
139///
140/// ```
141/// use str_cat::str_cat;
142///
143/// let mut s = str_cat!("Hello", " ", "World", "!");
144/// assert_eq!(s, "Hello World!");
145///
146/// // Reusing allocation.
147/// s.clear();
148/// str_cat!(&mut s; "foo", "bar");
149/// assert_eq!(s, "foobar");
150/// ```
151#[macro_export]
152macro_rules! str_cat {
153 (@stack $input:ident, $additional:ident; $($values_coerced:ident)*;) => {
154 $input.reserve($additional);
155 $($input.push_str($values_coerced);)*
156 };
157
158 (@stack $input:ident, $additional:ident; $($values_coerced:ident)*; $head:expr, $($tail:expr,)*) => {
159 match &$head {
160 value => {
161 let value_coerced: &str = &*value;
162 $additional += value_coerced.len();
163 $crate::str_cat!(@stack $input, $additional; $($values_coerced)* value_coerced; $($tail,)*);
164 }
165 }
166 };
167
168 ($input:expr; $($el:expr),+ $(,)?) => {{
169 #[allow(unused_mut)]
170 let mut input = $input;
171 let mut additional = 0;
172 $crate::str_cat!(@stack input, additional; ; $($el,)*);
173 input
174 }};
175
176 ($($el:expr),+ $(,)?) => {
177 $crate::str_cat!(::std::string::String::new(); $($el,)*)
178 };
179}
180
181/// Concatenate paths for a [`PathBuf`](std::path::PathBuf).
182///
183/// It requires all elements to implement [`AsRef<Path>`](AsRef).
184///
185/// # Example
186///
187/// ```
188/// use str_cat::path_cat;
189/// use std::ffi::OsStr;
190/// use std::path::{Path, PathBuf};
191///
192/// let mut s = path_cat!("Hello", "space".to_owned(), Path::new("World"), OsStr::new("bang"));
193/// assert_eq!(s, ["Hello", "space", "World", "bang"].iter().collect::<PathBuf>());
194///
195/// // Reusing allocation.
196/// s.clear();
197/// path_cat!(&mut s; "foo", "bar");
198/// assert_eq!(s, ["foo", "bar"].iter().collect::<PathBuf>());
199/// ```
200#[macro_export]
201macro_rules! path_cat {
202 (@stack $input:ident, $additional:ident; $($values_coerced:ident)*;) => {
203 $input.reserve($additional);
204 $($input.push($values_coerced);)*
205 };
206
207 (@stack $input:ident, $additional:ident; $($values_coerced:ident)*; $head:expr, $($tail:expr,)*) => {
208 match &$head {
209 value => {
210 let value_coerced = ::core::convert::AsRef::<::std::path::Path>::as_ref(&value);
211 $additional += value_coerced.as_os_str().len();
212 $crate::path_cat!(@stack $input, $additional; $($values_coerced)* value_coerced; $($tail,)*);
213 }
214 }
215 };
216
217 ($input:expr; $($el:expr),+ $(,)?) => {{
218 #[allow(unused_mut)]
219 let mut input = $input;
220 let mut additional = 0;
221 $crate::path_cat!(@stack input, additional; ; $($el,)*);
222 input
223 }};
224
225 ($($el:expr),+ $(,)?) => {
226 $crate::path_cat!(::std::path::PathBuf::new(); $($el,)*)
227 };
228}
229
230/// Concatenate OS strings for a [`OsString`](std::ffi::OsString).
231///
232/// It requires all elements to implement [`AsRef<OsStr>`](AsRef).
233///
234/// # Example
235///
236/// ```
237/// use str_cat::os_str_cat;
238/// use std::ffi::OsStr;
239/// use std::path::Path;
240///
241/// let mut s = os_str_cat!("Hello", " ".to_owned(), Path::new("World"), OsStr::new("!"));
242/// assert_eq!(s, OsStr::new("Hello World!"));
243///
244/// // Reusing allocation.
245/// s.clear();
246/// os_str_cat!(&mut s; "foo", "bar");
247/// assert_eq!(s, OsStr::new("foobar"));
248/// ```
249#[macro_export]
250macro_rules! os_str_cat {
251 (@stack $input:ident, $additional:ident; $($values_coerced:ident)*;) => {
252 $input.reserve($additional);
253 $($input.push($values_coerced);)*
254 };
255
256 (@stack $input:ident, $additional:ident; $($values_coerced:ident)*; $head:expr, $($tail:expr,)*) => {
257 match &$head {
258 value => {
259 let value_coerced = ::core::convert::AsRef::<::std::ffi::OsStr>::as_ref(&value);
260 $additional += value_coerced.len();
261 $crate::os_str_cat!(@stack $input, $additional; $($values_coerced)* value_coerced; $($tail,)*);
262 }
263 }
264 };
265
266 ($input:expr; $($el:expr),+ $(,)?) => {{
267 #[allow(unused_mut)]
268 let mut input = $input;
269 let mut additional = 0;
270 $crate::os_str_cat!(@stack input, additional; ; $($el,)*);
271 input
272 }};
273
274 ($($el:expr),+ $(,)?) => {
275 $crate::os_str_cat!(::std::ffi::OsString::new(); $($el,)*)
276 };
277}
278
279/// Concatenate elements for a [`Vec`](Vec).
280///
281/// # Example
282///
283/// ```
284/// use str_cat::vec_cat;
285/// use std::ffi::OsStr;
286/// use std::path::Path;
287///
288/// let mut s = vec_cat!(b"Hello", b" ", "World".as_bytes(), &[b'!']);
289/// assert_eq!(s, b"Hello World!");
290///
291/// // Reusing allocation.
292/// s.clear();
293/// vec_cat!(&mut s; b"foo", b"bar");
294/// assert_eq!(s, b"foobar");
295/// ```
296#[macro_export]
297macro_rules! vec_cat {
298 (@stack $input:ident, $additional:ident; $($values_coerced:ident)*;) => {
299 $input.reserve($additional);
300 $($input.extend_from_slice($values_coerced);)*
301 };
302
303 (@stack $input:ident, $additional:ident; $($values_coerced:ident)*; $head:expr, $($tail:expr,)*) => {
304 match &$head {
305 value => {
306 let value_coerced = ::core::convert::AsRef::<[_]>::as_ref(&value);
307 $additional += value_coerced.len();
308 $crate::vec_cat!(@stack $input, $additional; $($values_coerced)* value_coerced; $($tail,)*);
309 }
310 }
311 };
312
313 ($input:expr; $($el:expr),+ $(,)?) => {{
314 #[allow(unused_mut)]
315 let mut input = $input;
316 let mut additional = 0;
317 $crate::vec_cat!(@stack input, additional; ; $($el,)*);
318 input
319 }};
320
321 ($($el:expr),+ $(,)?) => {
322 $crate::vec_cat!(::std::vec![]; $($el,)*)
323 };
324}
325
326#[cfg(test)]
327mod tests {
328 #[test]
329 fn currently_doc_tests_covered_everything() {}
330}