compact_str/
traits.rs

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