compact_str/
traits.rs

1use core::fmt::{
2    self,
3    Write,
4};
5use core::num;
6
7use castaway::{
8    match_type,
9    LifetimeFree,
10};
11
12use super::repr::{
13    IntoRepr,
14    Repr,
15};
16use crate::CompactString;
17
18/// A trait for converting a value to a `CompactString`.
19///
20/// This trait is automatically implemented for any type which implements the
21/// [`fmt::Display`] trait. As such, [`ToCompactString`] shouldn't be implemented directly:
22/// [`fmt::Display`] should be implemented instead, and you get the [`ToCompactString`]
23/// implementation for free.
24pub trait ToCompactString {
25    /// Converts the given value to a [`CompactString`].
26    ///
27    /// # Examples
28    ///
29    /// Basic usage:
30    ///
31    /// ```
32    /// use compact_str::ToCompactString;
33    /// # use compact_str::CompactString;
34    ///
35    /// let i = 5;
36    /// let five = CompactString::new("5");
37    ///
38    /// assert_eq!(i.to_compact_string(), five);
39    /// ```
40    fn to_compact_string(&self) -> CompactString;
41}
42
43/// # Safety
44///
45/// * [`CompactString`] does not contain any lifetime
46/// * [`CompactString`] is 'static
47/// * [`CompactString`] is a container to `u8`, which is `LifetimeFree`.
48unsafe impl LifetimeFree for CompactString {}
49unsafe impl LifetimeFree for Repr {}
50
51/// # Panics
52///
53/// In this implementation, the `to_compact_string` method panics if the `Display` implementation
54/// returns an error. This indicates an incorrect `Display` implementation since
55/// `std::fmt::Write for CompactString` never returns an error itself.
56///
57/// # Note
58///
59/// We use the [`castaway`] crate to provide zero-cost specialization for several types, those are:
60/// * `u8`, `u16`, `u32`, `u64`, `u128`, `usize`
61/// * `i8`, `i16`, `i32`, `i64`, `i128`, `isize`
62/// * `NonZeroU*`, `NonZeroI*`
63/// * `bool`
64/// * `char`
65/// * `String`, `CompactString`
66/// * `f32`, `f64`
67///     * For floats we use [`ryu`] crate which sometimes provides different formatting than [`std`]
68impl<T: fmt::Display> ToCompactString for T {
69    #[inline]
70    fn to_compact_string(&self) -> CompactString {
71        let repr = match_type!(self, {
72            &u8 as s => s.into_repr(),
73            &i8 as s => s.into_repr(),
74            &u16 as s => s.into_repr(),
75            &i16 as s => s.into_repr(),
76            &u32 as s => s.into_repr(),
77            &i32 as s => s.into_repr(),
78            &u64 as s => s.into_repr(),
79            &i64 as s => s.into_repr(),
80            &u128 as s => s.into_repr(),
81            &i128 as s => s.into_repr(),
82            &usize as s => s.into_repr(),
83            &isize as s => s.into_repr(),
84            &f32 as s => s.into_repr(),
85            &f64 as s => s.into_repr(),
86            &bool as s => s.into_repr(),
87            &char as s => s.into_repr(),
88            &String as s => Repr::new(s),
89            &CompactString as s => Repr::new(s),
90            &num::NonZeroU8 as s => s.into_repr(),
91            &num::NonZeroI8 as s => s.into_repr(),
92            &num::NonZeroU16 as s => s.into_repr(),
93            &num::NonZeroI16 as s => s.into_repr(),
94            &num::NonZeroU32 as s => s.into_repr(),
95            &num::NonZeroI32 as s => s.into_repr(),
96            &num::NonZeroU64 as s => s.into_repr(),
97            &num::NonZeroI64 as s => s.into_repr(),
98            &num::NonZeroUsize as s => s.into_repr(),
99            &num::NonZeroIsize as s => s.into_repr(),
100            &num::NonZeroU128 as s => s.into_repr(),
101            &num::NonZeroI128 as s => s.into_repr(),
102            s => {
103                let mut c = CompactString::new_inline("");
104                write!(&mut c, "{}", s).expect("fmt::Display incorrectly implemented!");
105                return c;
106            }
107        });
108
109        CompactString(repr)
110    }
111}
112
113/// A trait that provides convience methods for creating a [`CompactString`] from a collection of
114/// items. It is implemented for all types that can be converted into an iterator, and that iterator
115/// yields types that can be converted into a `str`.
116///
117/// i.e. `C: IntoIterator<Item = AsRef<str>>`.
118///
119/// # Concatenate and Join
120/// Two methods that this trait provides are `concat_compact(...)` and `join_compact(...)`
121/// ```
122/// use compact_str::CompactStringExt;
123///
124/// let words = vec!["☀️", "🌕", "🌑", "☀️"];
125///
126/// // directly concatenate all the words together
127/// let concat = words.concat_compact();
128/// assert_eq!(concat, "☀️🌕🌑☀️");
129///
130/// // join the words, with a seperator
131/// let join = words.join_compact(" ➡️ ");
132/// assert_eq!(join, "☀️ ➡️ 🌕 ➡️ 🌑 ➡️ ☀️");
133/// ```
134pub trait CompactStringExt {
135    /// Concatenates all the items of a collection into a [`CompactString`]
136    ///
137    /// # Example
138    /// ```
139    /// use compact_str::CompactStringExt;
140    ///
141    /// let items = ["hello", " ", "world", "!"];
142    /// let compact = items.concat_compact();
143    ///
144    /// assert_eq!(compact, "hello world!");
145    /// ```
146    fn concat_compact(&self) -> CompactString;
147
148    /// Joins all the items of a collection, placing a seperator between them, forming a
149    /// [`CompactString`]
150    ///
151    /// # Example
152    /// ```
153    /// use compact_str::CompactStringExt;
154    ///
155    /// let fruits = vec!["apples", "oranges", "bananas"];
156    /// let compact = fruits.join_compact(", ");
157    ///
158    /// assert_eq!(compact, "apples, oranges, bananas");
159    /// ```
160    fn join_compact<S: AsRef<str>>(&self, seperator: S) -> CompactString;
161}
162
163impl<I, C> CompactStringExt for C
164where
165    I: AsRef<str>,
166    for<'a> &'a C: IntoIterator<Item = &'a I>,
167{
168    fn concat_compact(&self) -> CompactString {
169        self.into_iter()
170            .fold(CompactString::new_inline(""), |mut s, item| {
171                s.push_str(item.as_ref());
172                s
173            })
174    }
175
176    fn join_compact<S: AsRef<str>>(&self, seperator: S) -> CompactString {
177        let mut compact_string = CompactString::new_inline("");
178
179        let mut iter = self.into_iter().peekable();
180        let sep = seperator.as_ref();
181
182        while let Some(item) = iter.next() {
183            compact_string.push_str(item.as_ref());
184            if iter.peek().is_some() {
185                compact_string.push_str(sep);
186            }
187        }
188
189        compact_string
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use core::num;
196
197    use proptest::prelude::*;
198    use test_strategy::proptest;
199
200    use super::{
201        CompactStringExt,
202        ToCompactString,
203    };
204    use crate::CompactString;
205
206    #[test]
207    fn test_join() {
208        let slice = ["hello", "world"];
209        let c = slice.join_compact(" ");
210        assert_eq!(c, "hello world");
211
212        let vector = vec!["🍎", "🍊", "🍌"];
213        let c = vector.join_compact(",");
214        assert_eq!(c, "🍎,🍊,🍌");
215    }
216
217    #[proptest]
218    #[cfg_attr(miri, ignore)]
219    fn proptest_join(items: Vec<String>, seperator: String) {
220        let c: CompactString = items.join_compact(&seperator);
221        let s: String = items.join(&seperator);
222        assert_eq!(c, s);
223    }
224
225    #[test]
226    fn test_concat() {
227        let items = vec!["hello", "world"];
228        let c = items.join_compact(" ");
229        assert_eq!(c, "hello world");
230
231        let vector = vec!["🍎", "🍊", "🍌"];
232        let c = vector.concat_compact();
233        assert_eq!(c, "🍎🍊🍌");
234    }
235
236    #[proptest]
237    #[cfg_attr(miri, ignore)]
238    fn proptest_concat(items: Vec<String>) {
239        let c: CompactString = items.concat_compact();
240        let s: String = items.concat();
241        assert_eq!(c, s);
242    }
243
244    #[proptest]
245    #[cfg_attr(miri, ignore)]
246    fn proptest_to_compact_string_u8(val: u8) {
247        let compact = val.to_compact_string();
248        prop_assert_eq!(compact.as_str(), val.to_string());
249    }
250
251    #[proptest]
252    #[cfg_attr(miri, ignore)]
253    fn proptest_to_compact_string_i8(val: i8) {
254        let compact = val.to_compact_string();
255        prop_assert_eq!(compact.as_str(), val.to_string());
256    }
257
258    #[proptest]
259    #[cfg_attr(miri, ignore)]
260    fn proptest_to_compact_string_u16(val: u16) {
261        let compact = val.to_compact_string();
262        prop_assert_eq!(compact.as_str(), val.to_string());
263    }
264
265    #[proptest]
266    #[cfg_attr(miri, ignore)]
267    fn proptest_to_compact_string_i16(val: i16) {
268        let compact = val.to_compact_string();
269        prop_assert_eq!(compact.as_str(), val.to_string());
270    }
271    #[proptest]
272    #[cfg_attr(miri, ignore)]
273    fn proptest_to_compact_string_u32(val: u32) {
274        let compact = val.to_compact_string();
275        prop_assert_eq!(compact.as_str(), val.to_string());
276    }
277    #[proptest]
278    #[cfg_attr(miri, ignore)]
279    fn proptest_to_compact_string_i32(val: i32) {
280        let compact = val.to_compact_string();
281        prop_assert_eq!(compact.as_str(), val.to_string());
282    }
283    #[proptest]
284    #[cfg_attr(miri, ignore)]
285    fn proptest_to_compact_string_u64(val: u64) {
286        let compact = val.to_compact_string();
287        prop_assert_eq!(compact.as_str(), val.to_string());
288    }
289    #[proptest]
290    #[cfg_attr(miri, ignore)]
291    fn proptest_to_compact_string_i64(val: i64) {
292        let compact = val.to_compact_string();
293        prop_assert_eq!(compact.as_str(), val.to_string());
294    }
295    #[proptest]
296    #[cfg_attr(miri, ignore)]
297    fn proptest_to_compact_string_usize(val: usize) {
298        let compact = val.to_compact_string();
299        prop_assert_eq!(compact.as_str(), val.to_string());
300    }
301    #[proptest]
302    #[cfg_attr(miri, ignore)]
303    fn proptest_to_compact_string_isize(val: isize) {
304        let compact = val.to_compact_string();
305        prop_assert_eq!(compact.as_str(), val.to_string());
306    }
307    #[proptest]
308    #[cfg_attr(miri, ignore)]
309    fn proptest_to_compact_string_u128(val: u128) {
310        let compact = val.to_compact_string();
311        prop_assert_eq!(compact.as_str(), val.to_string());
312    }
313    #[proptest]
314    #[cfg_attr(miri, ignore)]
315    fn proptest_to_compact_string_i128(val: i128) {
316        let compact = val.to_compact_string();
317        prop_assert_eq!(compact.as_str(), val.to_string());
318    }
319
320    #[proptest]
321    #[cfg_attr(miri, ignore)]
322    fn proptest_to_compact_string_non_zero_u8(
323        #[strategy((1..=u8::MAX).prop_map(|x| unsafe { num::NonZeroU8::new_unchecked(x)} ))]
324        val: num::NonZeroU8,
325    ) {
326        let compact = val.to_compact_string();
327        prop_assert_eq!(compact.as_str(), val.to_string());
328    }
329
330    #[proptest]
331    #[cfg_attr(miri, ignore)]
332    fn proptest_to_compact_string_non_zero_u16(
333        #[strategy((1..=u16::MAX).prop_map(|x| unsafe { num::NonZeroU16::new_unchecked(x)} ))]
334        val: num::NonZeroU16,
335    ) {
336        let compact = val.to_compact_string();
337        prop_assert_eq!(compact.as_str(), val.to_string());
338    }
339
340    #[proptest]
341    #[cfg_attr(miri, ignore)]
342    fn proptest_to_compact_string_non_zero_u32(
343        #[strategy((1..=u32::MAX).prop_map(|x| unsafe { num::NonZeroU32::new_unchecked(x)} ))]
344        val: num::NonZeroU32,
345    ) {
346        let compact = val.to_compact_string();
347        prop_assert_eq!(compact.as_str(), val.to_string());
348    }
349
350    #[proptest]
351    #[cfg_attr(miri, ignore)]
352    fn proptest_to_compact_string_non_zero_u64(
353        #[strategy((1..=u64::MAX).prop_map(|x| unsafe { num::NonZeroU64::new_unchecked(x)} ))]
354        val: num::NonZeroU64,
355    ) {
356        let compact = val.to_compact_string();
357        prop_assert_eq!(compact.as_str(), val.to_string());
358    }
359
360    #[proptest]
361    #[cfg_attr(miri, ignore)]
362    fn proptest_to_compact_string_non_zero_u128(
363        #[strategy((1..=u128::MAX).prop_map(|x| unsafe { num::NonZeroU128::new_unchecked(x)} ))]
364        val: num::NonZeroU128,
365    ) {
366        let compact = val.to_compact_string();
367        prop_assert_eq!(compact.as_str(), val.to_string());
368    }
369
370    #[proptest]
371    #[cfg_attr(miri, ignore)]
372    fn proptest_to_compact_string_non_zero_usize(
373        #[strategy((1..=usize::MAX).prop_map(|x| unsafe { num::NonZeroUsize::new_unchecked(x)} ))]
374        val: num::NonZeroUsize,
375    ) {
376        let compact = val.to_compact_string();
377        prop_assert_eq!(compact.as_str(), val.to_string());
378    }
379
380    #[proptest]
381    #[cfg_attr(miri, ignore)]
382    fn proptest_to_compact_string_non_zero_i8(
383        #[strategy((1..=u8::MAX).prop_map(|x| unsafe { num::NonZeroI8::new_unchecked(x as i8)} ))]
384        val: num::NonZeroI8,
385    ) {
386        let compact = val.to_compact_string();
387        prop_assert_eq!(compact.as_str(), val.to_string());
388    }
389
390    #[proptest]
391    #[cfg_attr(miri, ignore)]
392    fn proptest_to_compact_string_non_zero_i16(
393        #[strategy((1..=u16::MAX).prop_map(|x| unsafe { num::NonZeroI16::new_unchecked(x as i16)} ))]
394        val: num::NonZeroI16,
395    ) {
396        let compact = val.to_compact_string();
397        prop_assert_eq!(compact.as_str(), val.to_string());
398    }
399
400    #[proptest]
401    #[cfg_attr(miri, ignore)]
402    fn proptest_to_compact_string_non_zero_i32(
403        #[strategy((1..=u32::MAX).prop_map(|x| unsafe { num::NonZeroI32::new_unchecked(x as i32)} ))]
404        val: num::NonZeroI32,
405    ) {
406        let compact = val.to_compact_string();
407        prop_assert_eq!(compact.as_str(), val.to_string());
408    }
409
410    #[proptest]
411    #[cfg_attr(miri, ignore)]
412    fn proptest_to_compact_string_non_zero_i64(
413        #[strategy((1..=u64::MAX).prop_map(|x| unsafe { num::NonZeroI64::new_unchecked(x as i64)} ))]
414        val: num::NonZeroI64,
415    ) {
416        let compact = val.to_compact_string();
417        prop_assert_eq!(compact.as_str(), val.to_string());
418    }
419
420    #[proptest]
421    #[cfg_attr(miri, ignore)]
422    fn proptest_to_compact_string_non_zero_i128(
423        #[strategy((1..=u128::MAX).prop_map(|x| unsafe { num::NonZeroI128::new_unchecked(x as i128)} ))]
424        val: num::NonZeroI128,
425    ) {
426        let compact = val.to_compact_string();
427        prop_assert_eq!(compact.as_str(), val.to_string());
428    }
429
430    #[proptest]
431    #[cfg_attr(miri, ignore)]
432    fn proptest_to_compact_string_non_zero_isize(
433        #[strategy((1..=usize::MAX).prop_map(|x| unsafe { num::NonZeroIsize::new_unchecked(x as isize)} ))]
434        val: num::NonZeroIsize,
435    ) {
436        let compact = val.to_compact_string();
437        prop_assert_eq!(compact.as_str(), val.to_string());
438    }
439}