[go: up one dir, main page]

compact_str/
traits.rs

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