mut_str/iter/
crs.rs

1use core::{iter::FusedIterator, str};
2
3use crate::{Char, OwnedChar};
4
5#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
6/// An iterator over references to the UTF-8 encoded characters of a [`prim@str`].
7///
8/// This is created by [`Self::from()`].
9///
10/// ```
11/// use mut_str::iter::CharRefs;
12///
13/// let s = "Hello, World!";
14///
15/// CharRefs::from(s)
16///     .zip(s.chars())
17///     .for_each(|(x, y)| assert_eq!(x, y));
18/// ```
19pub struct CharRefs<'a> {
20    s: &'a [u8],
21}
22
23impl<'a> CharRefs<'a> {
24    #[must_use]
25    #[inline]
26    /// Get the remaining string to be iterated over.
27    pub const fn as_str(&self) -> &'a str {
28        // SAFETY:
29        // `self.s` is guaranteed to be the bytes of a valid utf8 string.
30        unsafe { str::from_utf8_unchecked(self.s) }
31    }
32
33    #[inline]
34    /// Map the iterator to [`OwnedChar`] values.
35    pub fn owned(self) -> core::iter::Map<Self, fn(&Char) -> OwnedChar> {
36        self.map(Char::as_owned)
37    }
38}
39
40impl<'a> From<&'a str> for CharRefs<'a> {
41    #[inline]
42    fn from(value: &'a str) -> Self {
43        Self {
44            s: value.as_bytes(),
45        }
46    }
47}
48
49impl<'a> Iterator for CharRefs<'a> {
50    type Item = &'a Char;
51
52    fn next(&mut self) -> Option<Self::Item> {
53        // SAFETY:
54        // `self.s` is guaranteed to be the bytes of a valid utf8 string.
55        let char = unsafe { str::from_utf8_unchecked(self.s) }.chars().next()?;
56
57        let (char_slice, remaining) = self.s.split_at(char.len_utf8());
58        self.s = remaining;
59
60        // SAFETY:
61        // `char_slice` is guaranteed to be a valid utf8 string containing
62        // exactly one character.
63        Some(unsafe { &*Char::new_unchecked(char_slice.as_ptr()) })
64    }
65
66    #[inline]
67    fn size_hint(&self) -> (usize, Option<usize>) {
68        let len = self.s.len();
69        ((len + 3) / 4, Some(len))
70    }
71
72    #[inline]
73    fn count(self) -> usize
74    where
75        Self: Sized,
76    {
77        // SAFETY:
78        // `self.s` is guaranteed to be the bytes of a valid utf8 string.
79        unsafe { str::from_utf8_unchecked(self.s) }.chars().count()
80    }
81
82    #[inline]
83    fn last(mut self) -> Option<Self::Item>
84    where
85        Self: Sized,
86    {
87        self.next_back()
88    }
89
90    fn nth(&mut self, n: usize) -> Option<Self::Item> {
91        // SAFETY:
92        // `self.s` is guaranteed to be the bytes of a valid utf8 string.
93        let (index, char) = unsafe { str::from_utf8_unchecked(self.s) }
94            .char_indices()
95            .nth(n)?;
96
97        let (prefix, remaining) = self.s.split_at(index + char.len_utf8());
98        self.s = remaining;
99        let char_slice = &prefix[index..];
100
101        // SAFETY:
102        // `char_slice` is guaranteed to be a valid utf8 string containing
103        // exactly one character.
104        Some(unsafe { &*Char::new_unchecked(char_slice.as_ptr()) })
105    }
106}
107
108impl<'a> DoubleEndedIterator for CharRefs<'a> {
109    fn next_back(&mut self) -> Option<Self::Item> {
110        // SAFETY:
111        // `self.s` is guaranteed to be the bytes of a valid utf8 string.
112        let char = unsafe { str::from_utf8_unchecked(self.s) }
113            .chars()
114            .next_back()?;
115
116        let (remaining, char_slice) = self.s.split_at(self.s.len() - char.len_utf8());
117        self.s = remaining;
118
119        // SAFETY:
120        // `char_slice` is guaranteed to be a valid utf8 string containing
121        // exactly one character.
122        Some(unsafe { &*Char::new_unchecked(char_slice.as_ptr()) })
123    }
124
125    fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
126        // SAFETY:
127        // `self.s` is guaranteed to be the bytes of a valid utf8 string.
128        let (index, char) = unsafe { str::from_utf8_unchecked(self.s) }
129            .char_indices()
130            .nth_back(n)?;
131
132        let (remaining, prefix) = self.s.split_at(index);
133        self.s = remaining;
134        let char_slice = &prefix[..char.len_utf8()];
135
136        // SAFETY:
137        // `char_slice` is guaranteed to be a valid utf8 string containing
138        // exactly one character.
139        Some(unsafe { &*Char::new_unchecked(char_slice.as_ptr()) })
140    }
141}
142
143impl<'a> FusedIterator for CharRefs<'a> {}
144
145#[cfg(test)]
146mod test {
147    use crate::test::TEST_STR;
148
149    use super::CharRefs;
150
151    #[test]
152    fn test_forwards() {
153        let mut iter = CharRefs::from(TEST_STR);
154
155        for expected in TEST_STR.chars() {
156            let actual = iter.next().expect("expected a character ref");
157
158            assert_eq!(actual.len(), expected.len_utf8());
159            assert_eq!(actual.as_char(), expected);
160        }
161
162        assert!(iter.next().is_none(), "expected no more character refs");
163
164        let size_hint = iter.size_hint();
165        assert_eq!(size_hint.0, 0);
166        assert_eq!(size_hint.1, Some(0));
167    }
168
169    #[test]
170    fn test_nth() {
171        for step in 0..4 {
172            let mut iter = CharRefs::from(TEST_STR);
173            let mut expected_chars = TEST_STR.chars();
174
175            while let Some(expected) = expected_chars.nth(step) {
176                let actual = iter.nth(step).expect("expected a character ref");
177
178                assert_eq!(actual.len(), expected.len_utf8());
179                assert_eq!(actual.as_char(), expected);
180            }
181
182            assert!(iter.nth(step).is_none(), "expected no more character refs");
183        }
184
185        assert!(CharRefs::from(TEST_STR).nth(4).is_none());
186    }
187
188    #[test]
189    fn test_backwards() {
190        let mut iter = CharRefs::from(TEST_STR);
191
192        for expected in TEST_STR.chars().rev() {
193            let actual = iter.next_back().expect("expected a character ref");
194
195            assert_eq!(actual.len(), expected.len_utf8());
196            assert_eq!(actual.as_char(), expected);
197        }
198
199        assert!(
200            iter.next_back().is_none(),
201            "expected no more character refs"
202        );
203
204        let size_hint = iter.size_hint();
205        assert_eq!(size_hint.0, 0);
206        assert_eq!(size_hint.1, Some(0));
207    }
208
209    #[test]
210    fn test_nth_backwards() {
211        for step in 0..4 {
212            let mut iter = CharRefs::from(TEST_STR);
213            let mut expected_chars = TEST_STR.chars();
214
215            while let Some(expected) = expected_chars.nth_back(step) {
216                let actual = iter.nth_back(step).expect("expected a character ref");
217
218                assert_eq!(actual.len(), expected.len_utf8());
219                assert_eq!(actual.as_char(), expected);
220            }
221
222            assert!(
223                iter.nth_back(step).is_none(),
224                "expected no more character refs"
225            );
226        }
227
228        assert!(CharRefs::from(TEST_STR).nth_back(4).is_none());
229    }
230}