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}