join_string/
lib.rs

1//! A simple crate to join elements as a string, interspersing a separator between
2//! all elements.
3//!
4//! This is done somewhat efficiently, if possible. Meaning if the iterator is
5//! cheaply clonable you can directly print the result of [`Join::join()`]
6//! without creating a temporary [`String`] in memory. The [`Join::join()`]
7//! method will appear on anything that implements [`std::iter::IntoIterator`],
8//! meaning on all iterators and collections. The elements and the separator
9//! need to implement [`std::fmt::Display`]. Alternatively the
10//! [`Join::join_str()`] method can be used to join elements that only
11//! implement [`AsRef<str>`].
12//!
13//! # Examples
14//!
15//! ```
16//! use join_string::Join;
17//!
18//! assert_eq!(
19//!     "foo bar baz".split_whitespace().join(", ").into_string(),
20//!     "foo, bar, baz");
21//!
22//! println!("{}",
23//!     "foo bar baz".split_whitespace()
24//!         .map(|s| s.chars().rev().join(""))
25//!         .join(' '));
26//! // Output: oof rab zab
27//! ```
28//!
29//! You can also write the result more directly to a [`std::io::Write`] or
30//! [`std::fmt::Write`] even if the backing iterator doesn't implement
31//! [`Clone`](https://doc.rust-lang.org/std/clone/trait.Clone.html).
32//!
33//! ```
34//! # use join_string::Join;
35//! #
36//! # fn main() -> std::io::Result<()> {
37//! ["foo", "bar", "baz"].join(", ").write_io(std::io::stdout())?;
38//! # Ok(())
39//! # }
40//! ```
41//!
42//! ```
43//! # use join_string::Join;
44//! #
45//! # fn main() -> std::fmt::Result {
46//! let mut str = String::new();
47//! ["foo", "bar", "baz"].join(", ").write_fmt(&mut str)?;
48//! # Ok(())
49//! # }
50//! ```
51//!
52//! # Notes
53//!
54//! The standard library already provides a similar [`std::slice::Join`]
55//! trait on slices, but not on iterators, and the standard library version
56//! always directly returns a new [`String`]. Further there are multiple
57//! other similar crates that however work a bit differently, e.g. having
58//! more restrictions on element and separator types or always returning a
59//! [`String`].
60
61// =============================================================================
62//      struct Joiner
63// =============================================================================
64
65/// Helper struct that captures the iterator and separator for later joining.
66pub struct Joiner<I, S>
67where
68    I: std::iter::Iterator,
69    S: std::fmt::Display,
70{
71    iter: I,
72    sep: S,
73}
74
75impl<I, S> Joiner<I, S>
76where
77    I: std::iter::Iterator,
78    S: std::fmt::Display,
79{
80    /// Create a [`Joiner`] object.
81    ///
82    /// You can use this when implementing your own `join()` function.
83    #[inline]
84    pub fn new(iter: I, sep: S) -> Self {
85        Self { iter, sep }
86    }
87
88    /// Consumes the backing iterator of a [`Joiner`] and returns the joined elements as a new [`String`].
89    #[inline]
90    pub fn into_string(self) -> String
91    where I::Item: std::fmt::Display {
92        let mut buffer = String::new();
93        let _ = self.write_fmt(&mut buffer);
94        buffer
95    }
96
97    /// Consumes the backing iterator of a [`Joiner`] and writes the joined elements into a [`std::fmt::Write`].
98    pub fn write_fmt<W: std::fmt::Write>(mut self, mut writer: W) -> std::fmt::Result
99    where I::Item: std::fmt::Display {
100        if let Some(first) = self.iter.next() {
101            write!(writer, "{}", first)?;
102            for item in self.iter {
103                write!(writer, "{}{}", self.sep, item)?;
104            }
105        }
106        Ok(())
107    }
108
109    /// Consumes the backing iterator of a [`Joiner`] and writes the joined elements into a [`std::io::Write`].
110    pub fn write_io<W: std::io::Write>(mut self, mut writer: W) -> std::io::Result<()>
111    where I::Item: std::fmt::Display {
112        if let Some(first) = self.iter.next() {
113            write!(writer, "{}", first)?;
114            for item in self.iter {
115                write!(writer, "{}{}", self.sep, item)?;
116            }
117        }
118        Ok(())
119    }
120}
121
122impl<I, S> From<Joiner<I, S>> for String
123where
124    I: std::iter::Iterator,
125    S: std::fmt::Display,
126    I::Item: std::fmt::Display,
127{
128    #[inline]
129    fn from(value: Joiner<I, S>) -> Self {
130        value.into_string()
131    }
132}
133
134impl<I, S> Clone for Joiner<I, S>
135where
136    I: std::iter::Iterator,
137    S: std::fmt::Display,
138    I::Item: std::fmt::Display,
139    I: Clone,
140    S: Clone,
141{
142    #[inline]
143    fn clone(&self) -> Self {
144        Self {
145            iter: self.iter.clone(),
146            sep: self.sep.clone(),
147        }
148    }
149}
150
151impl<I, S> std::fmt::Display for Joiner<I, S>
152where
153    I: std::iter::Iterator,
154    S: std::fmt::Display,
155    I::Item: std::fmt::Display,
156    I: Clone,
157{
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        let mut iter = self.iter.clone();
160        if let Some(first) = iter.next() {
161            first.fmt(f)?;
162            for item in iter {
163                self.sep.fmt(f)?;
164                item.fmt(f)?;
165            }
166        }
167        Ok(())
168    }
169}
170
171impl<I, S> std::fmt::Debug for Joiner<I, S>
172where
173    I: std::iter::Iterator,
174    S: std::fmt::Display,
175    I::Item: std::fmt::Debug,
176    I: Clone,
177{
178    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179        let mut iter = self.iter.clone();
180        if let Some(first) = iter.next() {
181            first.fmt(f)?;
182            for item in iter {
183                self.sep.fmt(f)?;
184                item.fmt(f)?;
185            }
186        }
187        Ok(())
188    }
189}
190// =============================================================================
191//      trait Join
192// =============================================================================
193
194/// Trait that provides a method to join elements of an iterator, interspersing
195/// a separator between all elements.
196///
197/// This trait is implemented for anything that implements
198/// [`std::iter::IntoIterator`], which is e.g. arrays, slices, [`Vec`], and more.
199pub trait Join<I: std::iter::Iterator>: std::iter::IntoIterator<IntoIter = I> {
200    /// Join the elements of an iterator, interspersing a separator between
201    /// all elements.
202    ///
203    /// The elements and the separator need to implement [`std::fmt::Display`].
204    #[inline]
205    fn join<S>(self, sep: S) -> Joiner<I, S>
206    where
207        Self: Sized,
208        S: std::fmt::Display,
209    {
210        Joiner {
211            iter: self.into_iter(),
212            sep,
213        }
214    }
215
216    /// Join the elements of an iterator, interspersing a separator between
217    /// all elements.
218    ///
219    /// The elements and the separator need to implement [`AsRef<str>`].
220    #[inline]
221    fn join_str<S>(self, sep: S) -> Joiner<DisplayIter<I>, DisplayWrapper<S>>
222    where
223        Self: Sized,
224        S: AsRef<str>,
225        I::Item: AsRef<str>,
226    {
227        Joiner {
228            iter: DisplayIter {
229                iter: self.into_iter(),
230            },
231            sep: DisplayWrapper(sep),
232        }
233    }
234}
235
236impl<T> Join<T::IntoIter> for T where T: std::iter::IntoIterator {}
237
238// =============================================================================
239//      struct DisplayWrapper
240// =============================================================================
241
242/// Helper for joining elements that only implement [`AsRef<str>`], but not [`std::fmt::Display`].
243#[repr(transparent)]
244#[derive(Debug)]
245pub struct DisplayWrapper<T: AsRef<str>>(T);
246
247impl<T: AsRef<str>> DisplayWrapper<T> {
248    #[inline]
249    pub fn new(value: T) -> Self {
250        Self(value)
251    }
252}
253
254impl<T> std::fmt::Display for DisplayWrapper<T>
255where
256    T: AsRef<str>,
257{
258    #[inline]
259    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
260        self.0.as_ref().fmt(f)
261    }
262}
263
264impl<T> Clone for DisplayWrapper<T>
265where
266    T: AsRef<str>,
267    T: Clone,
268{
269    #[inline]
270    fn clone(&self) -> Self {
271        Self(self.0.clone())
272    }
273}
274
275impl<T> AsRef<str> for DisplayWrapper<T>
276where
277    T: AsRef<str>,
278{
279    #[inline]
280    fn as_ref(&self) -> &str {
281        self.0.as_ref()
282    }
283}
284
285// =============================================================================
286//      struct DisplayIter
287// =============================================================================
288
289/// Iterator-facade that maps an iterator over [`AsRef<str>`] to an iterator
290/// over [`DisplayWrapper`].
291///
292/// This is used to implement [`Join::join_str()`].
293#[derive(Debug)]
294pub struct DisplayIter<I>
295where
296    I: std::iter::Iterator,
297{
298    iter: I,
299}
300
301impl<I> DisplayIter<I>
302where
303    I: std::iter::Iterator,
304{
305    #[inline]
306    pub fn new(elements: impl Join<I>) -> Self {
307        Self {
308            iter: elements.into_iter(),
309        }
310    }
311}
312
313impl<I> std::iter::Iterator for DisplayIter<I>
314where
315    I: std::iter::Iterator,
316    I::Item: AsRef<str>,
317{
318    type Item = DisplayWrapper<I::Item>;
319
320    #[inline]
321    fn next(&mut self) -> Option<Self::Item> {
322        if let Some(item) = self.iter.next() {
323            return Some(DisplayWrapper(item));
324        }
325        None
326    }
327
328    #[inline]
329    fn last(self) -> Option<Self::Item>
330    where
331        Self: Sized,
332    {
333        if let Some(item) = self.iter.last() {
334            return Some(DisplayWrapper(item));
335        }
336        None
337    }
338
339    #[inline]
340    fn count(self) -> usize
341    where
342        Self: Sized,
343    {
344        self.iter.count()
345    }
346
347    #[inline]
348    fn nth(&mut self, n: usize) -> Option<Self::Item> {
349        self.iter.nth(n).map(DisplayWrapper)
350    }
351
352    #[inline]
353    fn size_hint(&self) -> (usize, Option<usize>) {
354        self.iter.size_hint()
355    }
356
357    #[inline]
358    #[cfg(target_feature = "iter_advance_by")]
359    fn advance_by(&mut self, n: usize) -> Result<(), NonZeroUsize> {
360        self.iter.advance_by(n)
361    }
362
363    #[inline]
364    #[cfg(target_feature = "trusted_random_access")]
365    unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item
366    where
367        Self: TrustedRandomAccessNoCoerce,
368    {
369        DisplayWrapper(self.iter.__iterator_get_unchecked(idx))
370    }
371}
372
373impl<I> std::iter::ExactSizeIterator for DisplayIter<I>
374where
375    I: std::iter::ExactSizeIterator,
376    I::Item: AsRef<str>,
377{
378    #[inline]
379    fn len(&self) -> usize {
380        self.iter.len()
381    }
382
383    #[inline]
384    #[cfg(target_feature = "exact_size_is_empty")]
385    fn is_empty(&self) -> bool {
386        self.iter.is_empty()
387    }
388}
389
390impl<I> std::iter::DoubleEndedIterator for DisplayIter<I>
391where
392    I: std::iter::DoubleEndedIterator,
393    I::Item: AsRef<str>,
394{
395    #[inline]
396    fn next_back(&mut self) -> Option<Self::Item> {
397        if let Some(item) = self.iter.next_back() {
398            return Some(DisplayWrapper(item));
399        }
400        None
401    }
402
403    #[inline]
404    fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
405        if let Some(item) = self.iter.nth_back(n) {
406            return Some(DisplayWrapper(item));
407        }
408        None
409    }
410
411    #[inline]
412    #[cfg(target_feature = "iter_advance_by")]
413    fn advance_back_by(&mut self, n: usize) -> Result<(), NonZeroUsize> {
414        self.iter.advance_back_by(n)
415    }
416}
417
418impl<I> Clone for DisplayIter<I>
419where
420    I: std::iter::Iterator,
421    I: Clone,
422{
423    #[inline]
424    fn clone(&self) -> Self {
425        Self {
426            iter: self.iter.clone(),
427        }
428    }
429}
430
431// =============================================================================
432//      functions
433// =============================================================================
434
435/// Join anything that implements [`Join`]. The elements need to implement
436/// [`std::fmt::Display`].
437///
438/// # Examples
439///
440/// ```
441/// use join_string::join;
442///
443/// assert_eq!(
444///     join(&["foo", "bar", "baz"], ", ").into_string(),
445///     "foo, bar, baz"
446/// );
447///
448/// assert_eq!(
449///     join([1, 2, 3].as_slice(), ", ").into_string(),
450///     "1, 2, 3"
451/// );
452///
453/// assert_eq!(
454///     join(&vec!['a', 'b', 'c'], ", ").into_string(),
455///     "a, b, c"
456/// );
457///
458/// assert_eq!(
459///     join([
460///         "foo".to_owned(),
461///         "bar".to_owned(),
462///         "baz".to_owned()
463///     ].iter().rev(), ", ").into_string(),
464///     "baz, bar, foo"
465/// );
466/// ```
467#[inline]
468pub fn join<I, S>(elements: impl Join<I>, sep: S) -> Joiner<I, S>
469where
470    I: std::iter::Iterator,
471    S: std::fmt::Display,
472{
473    elements.join(sep)
474}
475
476/// Join anything that implements [`Join`] when elements don't implement
477/// [`std::fmt::Display`], but implement [`AsRef<str>`] instead.
478///
479/// # Examples
480///
481/// ```
482/// use join_string::join_str;
483///
484/// assert_eq!(
485///     join_str(&["foo", "bar", "baz"], ", ").into_string(),
486///     "foo, bar, baz"
487/// );
488///
489/// assert_eq!(
490///     join_str([
491///         &"foo".to_owned(),
492///         &"bar".to_owned(),
493///         &"baz".to_owned()
494///     ].as_slice(), ", ").into_string(),
495///     "foo, bar, baz"
496/// );
497///
498/// assert_eq!(
499///     join_str(&vec!["foo", "bar", "baz"], ", ").into_string(),
500///     "foo, bar, baz"
501/// );
502///
503/// assert_eq!(
504///     join_str([
505///         "foo".to_owned(),
506///         "bar".to_owned(),
507///         "baz".to_owned()
508///     ].iter().rev(), ", ").into_string(),
509///     "baz, bar, foo"
510/// );
511/// ```
512#[inline]
513pub fn join_str<I, S>(
514    elements: impl Join<I>,
515    sep: S,
516) -> Joiner<impl std::iter::Iterator<Item = impl std::fmt::Display>, impl std::fmt::Display>
517where
518    I: std::iter::Iterator,
519    I::Item: AsRef<str>,
520    S: AsRef<str>,
521{
522    DisplayIter::new(elements).join(DisplayWrapper(sep))
523}