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}