str_chunks/
str_chunks.rs

1/*
2 * --------------------
3 * THIS FILE IS LICENSED UNDER THE FOLLOWING TERMS
4 *
5 * all rights reserved. be gay, do crime
6 *
7 * THE FOLLOWING MESSAGE IS NOT A LICENSE
8 *
9 * <barrow@tilde.team> wrote this file.
10 * by reading this text, you are reading "TRANS RIGHTS".
11 * this file and the content within it is the gay agenda.
12 * if we meet some day, and you think this stuff is worth it,
13 * you can buy me a beer, tea, or something stronger.
14 * -Ezra Barrow
15 * --------------------
16 */
17#![no_std]
18
19//! # str chunks
20//! implements char-wise chunked iteration of str
21//!
22//! the methods [`str_chunks`], [`str_chunks_exact`], [`str_rchunks`], and [`str_rchunks_exact`]
23//! behave like the similarly named methods on slice,
24//! but return string slices that are `chunk_size` chars long.
25//! take note: these slices are not necessarily `chunk_size` *bytes* long. `chunk.len() != chunk_size`
26//!
27//! import [`ImplStrChunks`] to get methods on [`&str`]
28//!
29//! For [`DoubleEndedIterator`] support, use the `reversable` method.
30//! This is not a trivial operation, and it is recommended to avoid it if possible.
31//!
32//! [`&str`]: str
33//! [`DoubleEndedIterator`]: core::iter::DoubleEndedIterator
34//! [`str_chunks`]: ImplStrChunks::str_chunks
35//! [`str_chunks_exact`]: ImplStrChunks::str_chunks_exact
36//! [`str_rchunks`]: ImplStrChunks::str_rchunks
37//! [`str_rchunks_exact`]: ImplStrChunks::str_rchunks_exact
38
39use core::{
40    iter::{once, Chain, Once},
41    marker::PhantomData,
42    num::NonZeroUsize,
43    str::CharIndices,
44};
45
46/// Trait implemented on &str to provide convenience methods.
47///
48/// Import this!
49///
50/// # Example
51/// ```
52/// use str_chunks::ImplStrChunks;
53/// let s = "lorem";
54/// let mut iter = s.str_chunks(2);
55/// assert_eq!(iter.next(), Some("lo"));
56/// assert_eq!(iter.next(), Some("re"));
57/// assert_eq!(iter.next(), Some("m"));
58/// assert_eq!(iter.next(), None);
59/// assert_eq!(iter.remaining(), "");
60/// ```
61pub trait ImplStrChunks {
62    /// Returns an iterator over `chunk_size` chars at a time,
63    /// starting at the beginning of the str.
64    /// The chunks are &str slices and do not overlap.
65    ///
66    /// If `chunk_size` does not divide the char-length of the str,
67    /// then the last chunk will not have char-length `chunk_size`.
68    ///
69    /// See [`str_chunks_exact`] for a variant of this iterator that returns chunks of always
70    /// exactly `chunk_size` elements, and [`str_rchunks`] for the same iterator but starting at
71    /// the end of the str.
72    ///
73    /// # Panics
74    /// Panics if `chunk_size` is 0.
75    ///
76    /// # Example
77    ///
78    /// ```
79    /// use str_chunks::*;
80    /// let s = "lorem";
81    /// let mut iter = s.str_chunks(2);
82    /// assert_eq!(iter.next(), Some("lo"));
83    /// assert_eq!(iter.next(), Some("re"));
84    /// assert_eq!(iter.next(), Some("m"));
85    /// assert_eq!(iter.next(), None);
86    /// assert_eq!(iter.remaining(), "");
87    /// ```
88    /// [`str_chunks`]: ImplStrChunks::str_chunks
89    /// [`str_chunks_exact`]: ImplStrChunks::str_chunks_exact
90    /// [`str_rchunks`]: ImplStrChunks::str_rchunks
91    /// [`str_rchunks_exact`]: ImplStrChunks::str_rchunks_exact
92    fn str_chunks(&self, chunk_size: usize) -> StrChunks;
93    /// Returns an iterator over `chunk_size` chars at a time,
94    /// starting at the beginning of the str.
95    /// The chunks are &str slices and do not overlap.
96    ///
97    /// If `chunk_size` does not divide the char-length of the str,
98    /// then the last up to `chunk_size-1` chars will be omitted and can be retrieved from the
99    /// [`remaining`] function of the iterator after the iterator has returned None.
100    ///
101    /// See [`str_chunks`] for a variant of this iterator that also returns the remainder as a
102    /// smaller chunk.
103    ///
104    /// # Panics
105    /// Panics if `chunk_size` is 0.
106    ///
107    /// # Example
108    ///
109    /// ```
110    /// use str_chunks::*;
111    /// let s = "lorem";
112    /// let mut iter = s.str_chunks_exact(2);
113    /// assert_eq!(iter.next(), Some("lo"));
114    /// assert_eq!(iter.next(), Some("re"));
115    /// assert_eq!(iter.next(), None);
116    /// assert_eq!(iter.remaining(), "m");
117    /// ```
118    /// [`str_chunks`]: ImplStrChunks::str_chunks
119    /// [`str_chunks_exact`]: ImplStrChunks::str_chunks_exact
120    /// [`str_rchunks`]: ImplStrChunks::str_rchunks
121    /// [`str_rchunks_exact`]: ImplStrChunks::str_rchunks_exact
122    /// [`remaining`]: StrChunksExact::remaining
123    fn str_chunks_exact(&self, chunk_size: usize) -> StrChunksExact;
124    /// Returns an iterator over `chunk_size` chars at a time,
125    /// starting at the end of the str.
126    /// The chunks are &str slices and do not overlap.
127    ///
128    /// If `chunk_size` does not divide the char-length of the str,
129    /// then the last chunk will not have char-length `chunk_size`.
130    ///
131    /// See [`str_rchunks_exact`] for a variant of this iterator that returns chunks of always
132    /// exactly `chunk_size` elements, and [`str_chunks`] for the same iterator but starting at the
133    /// beginning of the str.
134    ///
135    /// # Panics
136    /// Panics if `chunk_size` is 0.
137    ///
138    /// # Example
139    ///
140    /// ```
141    /// use str_chunks::*;
142    /// let s = "lorem";
143    /// let mut iter = s.str_rchunks(2);
144    /// assert_eq!(iter.next(), Some("em"));
145    /// assert_eq!(iter.next(), Some("or"));
146    /// assert_eq!(iter.next(), Some("l"));
147    /// assert_eq!(iter.next(), None);
148    /// assert_eq!(iter.remaining(), "");
149    /// ```
150    /// [`str_chunks`]: ImplStrChunks::str_chunks
151    /// [`str_chunks_exact`]: ImplStrChunks::str_chunks_exact
152    /// [`str_rchunks`]: ImplStrChunks::str_rchunks
153    /// [`str_rchunks_exact`]: ImplStrChunks::str_rchunks_exact
154    fn str_rchunks(&self, chunk_size: usize) -> StrRChunks;
155    /// Returns an iterator over `chunk_size` chars at a time,
156    /// starting at the end of the str.
157    /// The chunks are &str slices and do not overlap.
158    ///
159    /// If `chunk_size` does not divide the char-length of the str,
160    /// then the last up to `chunk_size-1` chars will be omitted and can be retrieved from the
161    /// [`remaining`] function of the iterator after the iterator has
162    /// returned None.
163    ///
164    /// See [`str_rchunks`] for a variant of this iterator that also returns the remainder as a
165    /// smaller chunk, and [`str_chunks_exact`] for the same iterator but starting at the beginning
166    /// of the str.
167    ///
168    /// # Panics
169    /// Panics if `chunk_size` is 0.
170    ///
171    /// # Example
172    ///
173    /// ```
174    /// use str_chunks::*;
175    /// let s = "lorem";
176    /// let mut iter = s.str_rchunks_exact(2);
177    /// assert_eq!(iter.next(), Some("em"));
178    /// assert_eq!(iter.next(), Some("or"));
179    /// assert_eq!(iter.next(), None);
180    /// assert_eq!(iter.remaining(), "l");
181    /// ```
182    /// [`str_chunks`]: ImplStrChunks::str_chunks
183    /// [`str_chunks_exact`]: ImplStrChunks::str_chunks_exact
184    /// [`str_rchunks`]: ImplStrChunks::str_rchunks
185    /// [`str_rchunks_exact`]: ImplStrChunks::str_rchunks_exact
186    /// [`remaining`]: StrChunksExact::remaining
187    fn str_rchunks_exact(&self, chunk_size: usize) -> StrRChunksExact;
188}
189impl ImplStrChunks for &str {
190    fn str_chunks(&self, chunk_size: usize) -> StrChunks {
191        assert!(chunk_size != 0, "chunk size must be non-zero");
192        // UNWRAP: already checking chunk_size is non-zero, unwrap will be optimized out
193        StrChunks::new(self, chunk_size.try_into().unwrap())
194    }
195    fn str_chunks_exact(&self, chunk_size: usize) -> StrChunksExact {
196        assert!(chunk_size != 0, "chunk size must be non-zero");
197        // UNWRAP: already checking chunk_size is non-zero, unwrap will be optimized out
198        StrChunksExact::new(self, chunk_size.try_into().unwrap())
199    }
200    fn str_rchunks(&self, chunk_size: usize) -> StrRChunks {
201        assert!(chunk_size != 0, "chunk size must be non-zero");
202        // UNWRAP: already checking chunk_size is non-zero, unwrap will be optimized out
203        StrRChunks::new(self, chunk_size.try_into().unwrap())
204    }
205    fn str_rchunks_exact(&self, chunk_size: usize) -> StrRChunksExact {
206        assert!(chunk_size != 0, "chunk size must be non-zero");
207        // UNWRAP: already checking chunk_size is non-zero, unwrap will be optimized out
208        StrRChunksExact::new(self, chunk_size.try_into().unwrap())
209    }
210}
211
212#[allow(clippy::doc_markdown)]
213/// str::char_indices does not include s.len() as an index.
214/// the length is, however, a valid index for str::split_at.
215/// this iterator adds the length of the str to the end of the char_indices
216fn valid_split_points(s: &str) -> Chain<CharIndices, Once<(usize, char)>> {
217    s.char_indices()
218        .chain(once((s.len(), char::REPLACEMENT_CHARACTER)))
219}
220
221/// An iterator over a str in (non-overlapping) chunks (`chunk_size` chars at a time) starting at
222/// the beginning of the str.
223///
224/// When the str len (in characters) is not evenly divided by the chunk size, the last str of
225/// the iteration will be the remainder.
226///
227/// This struct is created by the [`str_chunks`] method on `str`
228///
229/// # Example
230///
231/// ```
232/// use str_chunks::*;
233/// let s = "lorem";
234/// let mut iter = s.str_chunks(2);
235/// assert_eq!(iter.next(), Some("lo"));
236/// assert_eq!(iter.next(), Some("re"));
237/// assert_eq!(iter.next(), Some("m"));
238/// assert_eq!(iter.next(), None);
239/// assert_eq!(iter.remaining(), "");
240/// ```
241///
242/// [`str_chunks`]: ImplStrChunks::str_chunks
243pub struct StrChunks<'s> {
244    s: &'s str,
245    // 0 chunk_size makes no sense
246    chunk_size: NonZeroUsize,
247}
248
249impl<'s> StrChunks<'s> {
250    fn new(s: &'s str, chunk_size: NonZeroUsize) -> Self {
251        Self { s, chunk_size }
252    }
253    #[must_use]
254    /// Returns the remaining string slice that has not yet been iterated over.
255    pub fn remaining(&self) -> &'s str {
256        self.s
257    }
258    #[must_use]
259    /// Makes this iterator reversible.
260    ///
261    /// Reversing these iterators requires precomputing the number of characters in the string
262    /// so the remainder can be split off in advance. This is not a trivial operation, as the only
263    /// way to get the character is to iterate over them all.
264    ///
265    /// Keep in mind that because of remainders, reversing a [`StrChunks`] will not necessarily yield
266    /// the same items as a corresponding [`StrRChunks`].
267    pub fn reversable(self) -> DoubleEnded<'s, Self> {
268        DoubleEnded::<'s, Self>::new(self)
269    }
270}
271
272impl<'s> Iterator for StrChunks<'s> {
273    type Item = &'s str;
274    fn next(&mut self) -> Option<&'s str> {
275        let (index, _) = valid_split_points(self.s)
276            .nth(self.chunk_size.get())
277            // SAFETY: the end of a str is a valid byte offset, and is the boundary of a codepoint
278            // by definition of a str.
279            .unwrap_or((self.s.len(), char::REPLACEMENT_CHARACTER));
280        // SAFETY: indices from valid_split_points are valid byte offsets on the boundary of a codepoint
281        let (chunk, rest) = unsafe { self.s.split_at_checked(index).unwrap_unchecked() };
282        self.s = rest;
283        // chunk is empty if index is 0 and (no remainder or remainder was already returned)
284        if chunk.is_empty() {
285            None
286        } else {
287            Some(chunk)
288        }
289    }
290}
291
292/// An iterator over a str in (non-overlapping) chunks (`chunk_size` chars at a time) starting at
293/// the beginning of the str.
294///
295/// When the str len (in characters) is not evenly divided by the chunk size, the last up to
296/// `chunk_size-1` chars can will be omitted but can be retrieved with the [`remaining`] function
297/// after the iteration has returned None.
298///
299/// This struct is created by the [`str_chunks_exact`] method on `str`
300///
301/// # Example
302///
303/// ```
304/// use str_chunks::*;
305/// let s = "lorem";
306/// let mut iter = s.str_chunks_exact(2);
307/// assert_eq!(iter.next(), Some("lo"));
308/// assert_eq!(iter.next(), Some("re"));
309/// assert_eq!(iter.next(), None);
310/// assert_eq!(iter.remaining(), "m");
311/// ```
312///
313/// [`str_chunks_exact`]: ImplStrChunks::str_chunks_exact
314/// [`remaining`]: StrChunksExact::remaining
315pub struct StrChunksExact<'s> {
316    s: &'s str,
317    // 0 chunk_size makes no sense
318    chunk_size: NonZeroUsize,
319}
320
321impl<'s> StrChunksExact<'s> {
322    fn new(s: &'s str, chunk_size: NonZeroUsize) -> Self {
323        Self { s, chunk_size }
324    }
325    #[must_use]
326    /// Returns the remaining string slice that has not yet been iterated over.
327    /// If [`Self::next`] has returned None, this is the remainder, and is at most N-1 chars long.
328    pub fn remaining(&self) -> &'s str {
329        self.s
330    }
331    #[must_use]
332    /// Makes this iterator reversible.
333    ///
334    /// Reversing these iterators requires precomputing the number of characters in the string
335    /// so the remainder can be split off in advance. This is not a trivial operation, as the only
336    /// way to get the character is to iterate over them all.
337    ///
338    /// Keep in mind that because of remainders, reversing a [`StrChunks`] will not necessarily yield
339    /// the same items as a corresponding [`StrRChunks`].
340    pub fn reversable(self) -> DoubleEnded<'s, Self> {
341        DoubleEnded::<'s, Self>::new(self)
342    }
343}
344
345impl<'s> Iterator for StrChunksExact<'s> {
346    type Item = &'s str;
347    fn next(&mut self) -> Option<&'s str> {
348        // will short-circuit None if s.len() < chunk_size, leaving the remainder in s
349        let (index, _) = valid_split_points(self.s).nth(self.chunk_size.get())?;
350
351        // SAFETY: indices from valid_split_points are valid byte offsets on the boundary of a codepoint
352        let (chunk, rest) = unsafe { self.s.split_at_checked(index).unwrap_unchecked() };
353
354        self.s = rest;
355        Some(chunk)
356    }
357}
358
359/// An iterator over a str in (non-overlapping) chunks (`chunk_size` chars at a time) starting at
360/// the end of the str.
361///
362/// When the str len (in characters) is not evenly divided by the chunk size, the last str of
363/// the iteration will be the remainder.
364///
365/// This struct is created by the [`str_rchunks`] method on `str`
366///
367/// # Example
368///
369/// ```
370/// use str_chunks::*;
371/// let s = "lorem";
372/// let mut iter = s.str_rchunks(2);
373/// assert_eq!(iter.next(), Some("em"));
374/// assert_eq!(iter.next(), Some("or"));
375/// assert_eq!(iter.next(), Some("l"));
376/// assert_eq!(iter.next(), None);
377/// assert_eq!(iter.remaining(), "");
378/// ```
379///
380/// [`str_rchunks`]: ImplStrChunks::str_rchunks
381pub struct StrRChunks<'s> {
382    s: &'s str,
383    // 0 chunk_size makes no sense
384    chunk_size: NonZeroUsize,
385}
386
387impl<'s> StrRChunks<'s> {
388    fn new(s: &'s str, chunk_size: NonZeroUsize) -> Self {
389        Self { s, chunk_size }
390    }
391    #[must_use]
392    /// Returns the remaining string slice that has not yet been iterated over.
393    pub fn remaining(&self) -> &'s str {
394        self.s
395    }
396    #[must_use]
397    /// Makes this iterator reversible.
398    ///
399    /// Reversing these iterators requires precomputing the number of characters in the string
400    /// so the remainder can be split off in advance. This is not a trivial operation, as the only
401    /// way to get the character is to iterate over them all.
402    ///
403    /// Keep in mind that because of remainders, reversing a [`StrChunks`] will not necessarily yield
404    /// the same items as a corresponding [`StrRChunks`].
405    pub fn reversable(self) -> DoubleEnded<'s, Self> {
406        DoubleEnded::<'s, Self>::new(self)
407    }
408}
409
410impl<'s> Iterator for StrRChunks<'s> {
411    type Item = &'s str;
412    fn next(&mut self) -> Option<&'s str> {
413        let (index, _) = valid_split_points(self.s)
414            .nth_back(self.chunk_size.get())
415            // SAFETY: the start of a str is a valid byte offset, and is the boundary of a codepoint
416            // by definition of a str.
417            .unwrap_or((0, char::REPLACEMENT_CHARACTER));
418        // SAFETY: indices from valid_split_points are valid byte offsets on the boundary of a codepoint
419        let (rest, chunk) = unsafe { self.s.split_at_checked(index).unwrap_unchecked() };
420        self.s = rest;
421        // chunk is empty if index is 0 and (no remainder or remainder was already returned)
422        if chunk.is_empty() {
423            None
424        } else {
425            Some(chunk)
426        }
427    }
428}
429
430/// An iterator over a str in (non-overlapping) chunks (`chunk_size` chars at a time) starting at
431/// the end of the str.
432///
433/// When the str len (in characters) is not evenly divided by the chunk size, the last up to
434/// `chunk_size-1` chars can will be omitted but can be retrieved with the [`remaining`] function
435/// after the iteration has returned None.
436///
437/// This struct is created by the [`str_rchunks_exact`] method on `str`
438///
439/// # Example
440///
441/// ```
442/// use str_chunks::*;
443/// let s = "lorem";
444/// let mut iter = s.str_rchunks_exact(2);
445/// assert_eq!(iter.next(), Some("em"));
446/// assert_eq!(iter.next(), Some("or"));
447/// assert_eq!(iter.next(), None);
448/// assert_eq!(iter.remaining(), "l");
449/// ```
450///
451/// [`str_rchunks_exact`]: ImplStrChunks::str_rchunks_exact
452/// [`remaining`]: StrRChunksExact::remaining
453pub struct StrRChunksExact<'s> {
454    s: &'s str,
455    // 0 chunk_size makes no sense
456    chunk_size: NonZeroUsize,
457}
458
459impl<'s> StrRChunksExact<'s> {
460    fn new(s: &'s str, chunk_size: NonZeroUsize) -> Self {
461        Self { s, chunk_size }
462    }
463    #[must_use]
464    /// Returns the remaining string slice that has not yet been iterated over.
465    /// If [`Self::next`] has returned None, this is the remainder, and is at most N-1 chars long.
466    pub fn remaining(&self) -> &'s str {
467        self.s
468    }
469    #[must_use]
470    /// Makes this iterator reversible.
471    ///
472    /// Reversing these iterators requires precomputing the number of characters in the string
473    /// so the remainder can be split off in advance. This is not a trivial operation, as the only
474    /// way to get the character is to iterate over them all.
475    ///
476    /// Keep in mind that because of remainders, reversing a [`StrChunks`] will not necessarily yield
477    /// the same items as a corresponding [`StrRChunks`].
478    pub fn reversable(self) -> DoubleEnded<'s, Self> {
479        DoubleEnded::<'s, Self>::new(self)
480    }
481}
482
483impl<'s> Iterator for StrRChunksExact<'s> {
484    type Item = &'s str;
485    fn next(&mut self) -> Option<&'s str> {
486        // will short-circuit None if s.len() < chunk_size, leaving the remainder in s
487        let (index, _) = valid_split_points(self.s).nth_back(self.chunk_size.get())?;
488
489        // SAFETY: indices from valid_split_points are valid byte offsets on the boundary of a codepoint
490        let (rest, chunk) = unsafe { self.s.split_at_checked(index).unwrap_unchecked() };
491
492        self.s = rest;
493        Some(chunk)
494    }
495}
496
497/// A double-ended variant of a strchunks variant.
498/// This struct is created by the [`reversable`] method on any strchunks variant.
499///
500/// Behaves exactly the same as the variant it was produced from,
501/// but implements [`DoubleEndedIterator`], letting you iterate from both ends,
502/// as well as reverse the iterator.
503///
504/// When the str len (in characters) is not evenly divided by the chunk size, the last up to
505/// `chunk_size-1` chars is the "remainder".
506/// For Exact iterators, the remainder will be omitted, but can be retrieved with the [`remainder`] function
507/// at any time.
508/// For non-Exact iterators, the remainder is the last element to be yielded from the front,
509/// or the first to be yielded from the back.
510///
511///
512/// # Example
513///
514/// ```
515/// use str_chunks::*;
516/// let s = "lorem";
517/// let mut iter = s.str_chunks_exact(2).reversable();
518/// assert_eq!(iter.next_back(), Some("re"));
519/// assert_eq!(iter.next_back(), Some("lo"));
520/// assert_eq!(iter.next_back(), None);
521/// assert_eq!(iter.next(), None);
522/// assert_eq!(iter.remainder(), "m");
523///
524/// let mut iter = s.str_chunks(2).reversable();
525/// assert_eq!(iter.next_back(), Some("m"));
526/// assert_eq!(iter.next_back(), Some("re"));
527/// assert_eq!(iter.next_back(), Some("lo"));
528/// assert_eq!(iter.next_back(), None);
529/// assert_eq!(iter.next(), None);
530/// ```
531///
532/// [`reversable`]: StrChunks::reversable
533/// [`DoubleEndedIterator`]: core::iter::DoubleEndedIterator
534/// [`remainder`]: DoubleEnded::remainder
535pub struct DoubleEnded<'s, T> {
536    s: &'s str,
537    chunk_size: NonZeroUsize,
538    remainder: &'s str,
539    _inner: PhantomData<T>,
540}
541impl<'s, T> DoubleEnded<'s, T> {
542    #[must_use]
543    /// Returns the remaining string slice that has not yet been iterated over,
544    /// not including the remainder.
545    ///
546    /// Note this is NOT the same as the `remainder` function available on Exact variants.
547    pub fn remaining(&self) -> &'s str {
548        self.s
549    }
550}
551impl<'s> DoubleEnded<'s, StrChunks<'s>> {
552    #[allow(clippy::needless_pass_by_value)]
553    fn new(from: StrChunks<'s>) -> Self {
554        // rust stdlib includes some optimization for Chars.count(), but its still expensive
555        let char_length = from.s.chars().count();
556        let rem_size = char_length % from.chunk_size;
557        // SAFETY: length of valid_split_points is char_length+1, rem_size is at most char_length,
558        // char_length+1 > char_length >= rem_size
559        let rem_index = unsafe {
560            valid_split_points(from.s)
561                .nth_back(rem_size)
562                .unwrap_unchecked()
563                .0
564        };
565        let (s, remainder) = from.s.split_at(rem_index);
566        Self {
567            s,
568            chunk_size: from.chunk_size,
569            remainder,
570            _inner: PhantomData,
571        }
572    }
573}
574impl<'s> DoubleEnded<'s, StrRChunks<'s>> {
575    #[allow(clippy::needless_pass_by_value)]
576    fn new(from: StrRChunks<'s>) -> Self {
577        // rust stdlib includes some optimization for Chars.count(), but its still expensive
578        let char_length = from.s.chars().count();
579        let rem_size = char_length % from.chunk_size;
580        // SAFETY: length of valid_split_points is char_length+1, rem_size is at most char_length,
581        // char_length+1 > char_length >= rem_size
582        let rem_index = unsafe {
583            valid_split_points(from.s)
584                .nth(rem_size)
585                .unwrap_unchecked()
586                .0
587        };
588        let (remainder, s) = from.s.split_at(rem_index);
589        Self {
590            s,
591            chunk_size: from.chunk_size,
592            remainder,
593            _inner: PhantomData,
594        }
595    }
596}
597impl<'s> DoubleEnded<'s, StrChunksExact<'s>> {
598    #[allow(clippy::needless_pass_by_value)]
599    fn new(from: StrChunksExact<'s>) -> Self {
600        let n = DoubleEnded::<'s, StrChunks<'s>>::new(StrChunks::new(from.s, from.chunk_size));
601        Self {
602            s: n.s,
603            chunk_size: n.chunk_size,
604            remainder: n.remainder,
605            _inner: PhantomData,
606        }
607    }
608    #[must_use]
609    /// Returns the remainder of the original string that is not going to be returned by the
610    /// iterator. the returned string has at most `chunk_size-1` characters.
611    pub fn remainder(&self) -> &'s str {
612        self.remainder
613    }
614}
615impl<'s> DoubleEnded<'s, StrRChunksExact<'s>> {
616    #[allow(clippy::needless_pass_by_value)]
617    fn new(from: StrRChunksExact<'s>) -> Self {
618        let n = DoubleEnded::<'s, StrRChunks<'s>>::new(StrRChunks::new(from.s, from.chunk_size));
619        Self {
620            s: n.s,
621            chunk_size: n.chunk_size,
622            remainder: n.remainder,
623            _inner: PhantomData,
624        }
625    }
626    #[must_use]
627    /// Returns the remainder of the original string that is not going to be returned by the
628    /// iterator. the returned string has at most `chunk_size-1` characters.
629    pub fn remainder(&self) -> &'s str {
630        self.remainder
631    }
632}
633impl<'s> Iterator for DoubleEnded<'s, StrChunks<'s>> {
634    type Item = &'s str;
635    fn next(&mut self) -> Option<Self::Item> {
636        // StrChunks::new doesnt do any processing or anything, this should get optimized well
637        let mut inner = StrChunks::new(self.s, self.chunk_size);
638        let next = inner.next().or_else(|| {
639            if self.remainder.is_empty() {
640                None
641            } else {
642                // SAFETY: 0 is a valid code point boundary
643                let (empty, remainder) =
644                    unsafe { self.remainder.split_at_checked(0).unwrap_unchecked() };
645                self.remainder = empty;
646                Some(remainder)
647            }
648        });
649        self.s = inner.s;
650        next
651    }
652}
653impl<'s> DoubleEndedIterator for DoubleEnded<'s, StrChunks<'s>> {
654    fn next_back(&mut self) -> Option<Self::Item> {
655        if self.remainder.is_empty() {
656            let mut inner = StrRChunks::new(self.s, self.chunk_size);
657            let next = inner.next();
658            self.s = inner.s;
659            next
660        } else {
661            // SAFETY: 0 is a valid code point boundary
662            let (empty, remainder) =
663                unsafe { self.remainder.split_at_checked(0).unwrap_unchecked() };
664            self.remainder = empty;
665            Some(remainder)
666        }
667    }
668}
669impl<'s> Iterator for DoubleEnded<'s, StrChunksExact<'s>> {
670    type Item = &'s str;
671    fn next(&mut self) -> Option<Self::Item> {
672        let mut inner = StrChunksExact::new(self.s, self.chunk_size);
673        let next = inner.next();
674        self.s = inner.s;
675        next
676    }
677}
678impl<'s> DoubleEndedIterator for DoubleEnded<'s, StrChunksExact<'s>> {
679    fn next_back(&mut self) -> Option<Self::Item> {
680        let mut inner = StrRChunks::new(self.s, self.chunk_size);
681        let next = inner.next();
682        self.s = inner.s;
683        next
684    }
685}
686impl<'s> Iterator for DoubleEnded<'s, StrRChunks<'s>> {
687    type Item = &'s str;
688    fn next(&mut self) -> Option<Self::Item> {
689        let mut inner = StrRChunks::new(self.s, self.chunk_size);
690        let next = inner.next().or_else(|| {
691            if self.remainder.is_empty() {
692                None
693            } else {
694                // SAFETY: 0 is a valid code point boundary
695                let (empty, remainder) =
696                    unsafe { self.remainder.split_at_checked(0).unwrap_unchecked() };
697                self.remainder = empty;
698                Some(remainder)
699            }
700        });
701        self.s = inner.s;
702        next
703    }
704}
705impl<'s> DoubleEndedIterator for DoubleEnded<'s, StrRChunks<'s>> {
706    fn next_back(&mut self) -> Option<Self::Item> {
707        if self.remainder.is_empty() {
708            let mut inner = StrChunks::new(self.s, self.chunk_size);
709            let next = inner.next();
710            self.s = inner.s;
711            next
712        } else {
713            // SAFETY: 0 is a valid code point boundary
714            let (empty, remainder) =
715                unsafe { self.remainder.split_at_checked(0).unwrap_unchecked() };
716            self.remainder = empty;
717            Some(remainder)
718        }
719    }
720}
721impl<'s> Iterator for DoubleEnded<'s, StrRChunksExact<'s>> {
722    type Item = &'s str;
723    fn next(&mut self) -> Option<Self::Item> {
724        let mut inner = StrRChunks::new(self.s, self.chunk_size);
725        let next = inner.next();
726        self.s = inner.s;
727        next
728    }
729}
730impl<'s> DoubleEndedIterator for DoubleEnded<'s, StrRChunksExact<'s>> {
731    fn next_back(&mut self) -> Option<Self::Item> {
732        let mut inner = StrChunks::new(self.s, self.chunk_size);
733        let next = inner.next();
734        self.s = inner.s;
735        next
736    }
737}
738
739#[cfg(test)]
740mod tests {
741    use super::*;
742
743    macro_rules! assert_chunks_fore {
744        ($iter:expr, $slice:expr, $remainder:expr) => {{
745            let mut iter = $iter;
746            let mut slice_iter = $slice.into_iter();
747            loop {
748                let i1 = iter.next();
749                let i2 = slice_iter.next();
750                if i1.is_none() && i2.is_none() {
751                    // done with iteration, check remainder
752                    break;
753                } else {
754                    assert_eq!(i1.as_ref(), i2);
755                }
756            }
757            assert_eq!(iter.remaining(), $remainder);
758            iter
759        }};
760    }
761    macro_rules! assert_chunks_back {
762        ($iter:expr, $slice:expr, $remainder:expr) => {{
763            let mut iter = $iter;
764            let mut slice_iter = $slice.into_iter();
765            loop {
766                let i1 = iter.next_back();
767                let i2 = slice_iter.next_back();
768                if i1.is_none() && i2.is_none() {
769                    // done with iteration, check remainder
770                    break;
771                } else {
772                    assert_eq!(i1.as_ref(), i2);
773                }
774            }
775            assert_eq!(iter.remaining(), $remainder);
776            iter
777        }};
778    }
779    macro_rules! assert_chunks_reversable {
780        ($iter:expr, $slice:expr) => {
781            assert_chunks_fore!($iter.reversable(), $slice, "");
782            assert_chunks_back!($iter.reversable(), $slice, "");
783        };
784        ($iter:expr, $slice:expr, $remainder:expr) => {
785            let fore = assert_chunks_fore!($iter.reversable(), $slice, "");
786            let back = assert_chunks_back!($iter.reversable(), $slice, "");
787            assert_eq!(fore.remainder(), $remainder);
788            assert_eq!(back.remainder(), $remainder);
789        };
790    }
791
792    macro_rules! assert_chunks {
793        ($iter:expr, $slice:expr, $remainder:expr) => {
794            assert_chunks_fore!($iter, $slice, $remainder);
795            assert_chunks_reversable!($iter, $slice);
796        };
797        (exact $iter:expr, $slice:expr, $remainder:expr) => {
798            assert_chunks_fore!($iter, $slice, $remainder);
799            assert_chunks_reversable!($iter, $slice, $remainder);
800        };
801    }
802
803    #[test]
804    fn str_chunks() {
805        let straight_str = "012345";
806        assert_chunks!(
807            straight_str.str_chunks(1),
808            &["0", "1", "2", "3", "4", "5"],
809            ""
810        );
811        assert_chunks!(straight_str.str_chunks(2), &["01", "23", "45"], "");
812        assert_chunks!(straight_str.str_chunks(3), &["012", "345"], "");
813        assert_chunks!(straight_str.str_chunks(4), &["0123", "45"], "");
814        assert_chunks!(straight_str.str_chunks(5), &["01234", "5"], "");
815        assert_chunks!(straight_str.str_chunks(6), &["012345"], "");
816    }
817    #[test]
818    fn str_chunks_exact() {
819        let straight_str = "012345";
820        assert_chunks!(exact
821            straight_str.str_chunks_exact(1),
822            &["0", "1", "2", "3", "4", "5"],
823            ""
824        );
825        assert_chunks!(exact straight_str.str_chunks_exact(2), &["01", "23", "45"], "");
826        assert_chunks!(exact straight_str.str_chunks_exact(3), &["012", "345"], "");
827        assert_chunks!(exact straight_str.str_chunks_exact(4), &["0123"], "45");
828        assert_chunks!(exact straight_str.str_chunks_exact(5), &["01234"], "5");
829        assert_chunks!(exact straight_str.str_chunks_exact(6), &["012345"], "");
830    }
831    #[test]
832    fn str_rchunks() {
833        let straight_str = "012345";
834        assert_chunks!(
835            straight_str.str_rchunks(1),
836            &["5", "4", "3", "2", "1", "0"],
837            ""
838        );
839        assert_chunks!(straight_str.str_rchunks(2), &["45", "23", "01"], "");
840        assert_chunks!(straight_str.str_rchunks(3), &["345", "012"], "");
841        assert_chunks!(straight_str.str_rchunks(4), &["2345", "01"], "");
842        assert_chunks!(straight_str.str_rchunks(5), &["12345", "0"], "");
843        assert_chunks!(straight_str.str_rchunks(6), &["012345"], "");
844    }
845    #[test]
846    fn str_rchunks_exact() {
847        let straight_str = "012345";
848        assert_chunks!(exact
849            straight_str.str_rchunks_exact(1),
850            &["5", "4", "3", "2", "1", "0"],
851            ""
852        );
853        assert_chunks!(exact straight_str.str_rchunks_exact(2), &["45", "23", "01"], "");
854        assert_chunks!(exact straight_str.str_rchunks_exact(3), &["345", "012"], "");
855        assert_chunks!(exact straight_str.str_rchunks_exact(4), &["2345"], "01");
856        assert_chunks!(exact straight_str.str_rchunks_exact(5), &["12345"], "0");
857        assert_chunks!(exact straight_str.str_rchunks_exact(6), &["012345"], "");
858    }
859    #[test]
860    fn emoji() {
861        // these are multi-byte characters
862        let s = "πŸ₯ΊπŸ’™πŸ˜΅";
863        assert_chunks!(s.str_chunks(2), &["πŸ₯ΊπŸ’™", "😡"], "");
864        assert_chunks!(exact s.str_chunks_exact(2), &["πŸ₯ΊπŸ’™"], "😡");
865        assert_chunks!(s.str_rchunks(2), &["πŸ’™πŸ˜΅", "πŸ₯Ί"], "");
866        assert_chunks!(exact s.str_rchunks_exact(2), &["πŸ’™πŸ˜΅"], "πŸ₯Ί");
867    }
868    #[test]
869    fn empty() {
870        let s = "";
871        assert_chunks!(s.str_chunks(2), &[], "");
872        assert_chunks!(exact s.str_chunks_exact(2), &[], "");
873        assert_chunks!(s.str_rchunks(2), &[], "");
874        assert_chunks!(exact s.str_rchunks_exact(2), &[], "");
875    }
876    #[test]
877    fn short() {
878        let s = "f";
879        assert_chunks!(s.str_chunks(2), &["f"], "");
880        assert_chunks!(exact s.str_chunks_exact(2), &[], "f");
881        assert_chunks!(s.str_rchunks(2), &["f"], "");
882        assert_chunks!(exact s.str_rchunks_exact(2), &[], "f");
883    }
884    #[test]
885    fn remainder() {
886        let s = "foo";
887        assert_chunks!(s.str_chunks(5), &["foo"], "");
888        assert_chunks!(exact s.str_chunks_exact(5), &[], "foo");
889        assert_chunks!(s.str_rchunks(5), &["foo"], "");
890        assert_chunks!(exact s.str_rchunks_exact(5), &[], "foo");
891    }
892    #[test]
893    fn reverse() {
894        let s = "0123456";
895        assert_chunks_reversable!(s.str_chunks(3), &["012", "345", "6"]);
896        assert_chunks_reversable!(s.str_rchunks(3), &["456", "123", "0"]);
897        assert_chunks_reversable!(s.str_chunks_exact(3), &["012", "345"], "6");
898        assert_chunks_reversable!(s.str_rchunks_exact(3), &["456", "123"], "0");
899    }
900}