mut_str/iter/
cmrs.rs

1use core::{iter::FusedIterator, mem::transmute, str};
2
3use crate::{Char, OwnedChar};
4
5#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
6/// An iterator over mutable references to the UTF-8 encoded characters of a mutable [`prim@str`].
7///
8/// This is created by [`Self::from()`].
9///
10/// ```
11/// use mut_str::iter::CharMutRefs;
12///
13/// let s = "Hello, World!";
14/// let mut owned_s = Box::<str>::from(s);
15///
16/// CharMutRefs::from(&mut *owned_s)
17///     .zip(s.chars())
18///     .for_each(|(x, y)| assert_eq!(x, y));
19/// ```
20pub struct CharMutRefs<'a> {
21    s: &'a mut [u8],
22}
23
24#[allow(clippy::needless_lifetimes)]
25impl<'a> CharMutRefs<'a> {
26    #[must_use]
27    #[inline]
28    /// Get the remaining string to be iterated over.
29    pub const fn as_str<'b>(&'b self) -> &'b str {
30        // SAFETY:
31        // `self.s` is guaranteed to be the bytes of a valid utf8 string.
32        unsafe { str::from_utf8_unchecked(self.s) }
33    }
34
35    #[must_use]
36    #[inline]
37    /// Get the remaining mutable string to be iterated over.
38    pub fn as_str_mut<'b>(&'b mut self) -> &'b mut str {
39        // SAFETY:
40        // `self.s` is guaranteed to be the bytes of a valid utf8 string.
41        unsafe { str::from_utf8_unchecked_mut(self.s) }
42    }
43
44    #[inline]
45    /// Map the iterator to [`OwnedChar`] values.
46    pub fn owned(self) -> core::iter::Map<Self, fn(&mut Char) -> OwnedChar> {
47        self.map(|x| x.as_owned())
48    }
49}
50
51impl<'a> From<&'a mut str> for CharMutRefs<'a> {
52    #[inline]
53    fn from(value: &'a mut str) -> Self {
54        Self {
55            // SAFETY:
56            // `Self` ensures that the string remains valid utf8.
57            s: unsafe { value.as_bytes_mut() },
58        }
59    }
60}
61
62impl<'a> Iterator for CharMutRefs<'a> {
63    type Item = &'a mut Char;
64
65    fn next<'b>(&'b mut self) -> Option<Self::Item> {
66        // SAFETY:
67        // `self.s` is guaranteed to be the bytes of a valid utf8 string.
68        let char = unsafe { str::from_utf8_unchecked(self.s) }.chars().next()?;
69
70        // SAFETY:
71        // As the string slice is split each time, this iterator will not hand out
72        // multiple mutable references to the same data, so extending its lifetime
73        // to `'a` is valid.
74        let (char_slice, remaining) = unsafe {
75            transmute::<(&'b mut [u8], &'b mut [u8]), (&'a mut [u8], &'a mut [u8])>(
76                self.s.split_at_mut(char.len_utf8()),
77            )
78        };
79
80        self.s = remaining;
81
82        // SAFETY:
83        // `char_slice` is guaranteed to be a valid utf8 string containing
84        // exactly one character.
85        Some(unsafe { &mut *Char::new_unchecked_mut(char_slice.as_mut_ptr()) })
86    }
87
88    #[inline]
89    fn size_hint(&self) -> (usize, Option<usize>) {
90        let len = self.s.len();
91        ((len + 3) / 4, Some(len))
92    }
93
94    #[inline]
95    fn count(self) -> usize
96    where
97        Self: Sized,
98    {
99        // SAFETY:
100        // `self.s` is guaranteed to be the bytes of a valid utf8 string.
101        unsafe { str::from_utf8_unchecked(self.s) }.chars().count()
102    }
103
104    #[inline]
105    fn last(mut self) -> Option<Self::Item>
106    where
107        Self: Sized,
108    {
109        self.next_back()
110    }
111
112    fn nth<'b>(&'b mut self, n: usize) -> Option<Self::Item> {
113        // SAFETY:
114        // `self.s` is guaranteed to be the bytes of a valid utf8 string.
115        let (index, char) = unsafe { str::from_utf8_unchecked(self.s) }
116            .char_indices()
117            .nth(n)?;
118
119        // SAFETY:
120        // As the string slice is split each time, this iterator will not hand out
121        // multiple mutable references to the same data, so extending its lifetime
122        // to `'a` is valid.
123        let (prefix, remaining) = unsafe {
124            transmute::<(&'b mut [u8], &'b mut [u8]), (&'a mut [u8], &'a mut [u8])>(
125                self.s.split_at_mut(index + char.len_utf8()),
126            )
127        };
128
129        self.s = remaining;
130        let char_slice = &mut prefix[index..];
131
132        // SAFETY:
133        // `char_slice` is guaranteed to be a valid utf8 string containing
134        // exactly one character.
135        Some(unsafe { &mut *Char::new_unchecked_mut(char_slice.as_mut_ptr()) })
136    }
137}
138
139impl<'a> DoubleEndedIterator for CharMutRefs<'a> {
140    fn next_back<'b>(&'b mut self) -> Option<Self::Item> {
141        // SAFETY:
142        // `self.s` is guaranteed to be the bytes of a valid utf8 string.
143        let char = unsafe { str::from_utf8_unchecked(self.s) }
144            .chars()
145            .next_back()?;
146
147        // SAFETY:
148        // As the string slice is split each time, this iterator will not hand out
149        // multiple mutable references to the same data, so extending its lifetime
150        // to `'a` is valid.
151        let (remaining, char_slice) = unsafe {
152            transmute::<(&'b mut [u8], &'b mut [u8]), (&'a mut [u8], &'a mut [u8])>(
153                self.s.split_at_mut(self.s.len() - char.len_utf8()),
154            )
155        };
156
157        self.s = remaining;
158
159        // SAFETY:
160        // `char_slice` is guaranteed to be a valid utf8 string containing
161        // exactly one character.
162        Some(unsafe { &mut *Char::new_unchecked_mut(char_slice.as_mut_ptr()) })
163    }
164
165    fn nth_back<'b>(&'b mut self, n: usize) -> Option<Self::Item> {
166        // SAFETY:
167        // `self.s` is guaranteed to be the bytes of a valid utf8 string.
168        let (index, char) = unsafe { str::from_utf8_unchecked(self.s) }
169            .char_indices()
170            .nth_back(n)?;
171
172        // SAFETY:
173        // As the string slice is split each time, this iterator will not hand out
174        // multiple mutable references to the same data, so extending its lifetime
175        // to `'a` is valid.
176        let (remaining, prefix) = unsafe {
177            transmute::<(&'b mut [u8], &'b mut [u8]), (&'a mut [u8], &'a mut [u8])>(
178                self.s.split_at_mut(index),
179            )
180        };
181
182        self.s = remaining;
183        let char_slice = &mut prefix[..char.len_utf8()];
184
185        // SAFETY:
186        // `char_slice` is guaranteed to be a valid utf8 string containing
187        // exactly one character.
188        Some(unsafe { &mut *Char::new_unchecked_mut(char_slice.as_mut_ptr()) })
189    }
190}
191
192impl<'a> FusedIterator for CharMutRefs<'a> {}
193
194#[cfg(test)]
195mod test {
196    use crate::{
197        test::{test_str_owned, TEST_STR},
198        StrExt,
199    };
200
201    use super::CharMutRefs;
202
203    #[test]
204    fn test_forwards() {
205        let mut s = test_str_owned();
206        let mut iter = CharMutRefs::from(&mut *s);
207
208        for expected in TEST_STR.chars() {
209            let actual = iter.next().expect("expected a character ref");
210
211            assert_eq!(actual.len(), expected.len_utf8());
212            assert_eq!(actual.as_char(), expected);
213        }
214
215        assert!(iter.next().is_none(), "expected no more character refs");
216
217        let size_hint = iter.size_hint();
218        assert_eq!(size_hint.0, 0);
219        assert_eq!(size_hint.1, Some(0));
220    }
221
222    #[test]
223    fn test_nth() {
224        let mut s = test_str_owned();
225
226        for step in 0..4 {
227            let mut iter = CharMutRefs::from(&mut *s);
228            let mut expected_chars = TEST_STR.chars();
229
230            while let Some(expected) = expected_chars.nth(step) {
231                let actual = iter.nth(step).expect("expected a character ref");
232
233                assert_eq!(actual.len(), expected.len_utf8());
234                assert_eq!(actual.as_char(), expected);
235            }
236
237            assert!(iter.nth(step).is_none(), "expected no more character refs");
238        }
239
240        assert!(CharMutRefs::from(&mut *s).nth(4).is_none());
241    }
242
243    #[test]
244    fn test_backwards() {
245        let mut s = test_str_owned();
246        let mut iter = CharMutRefs::from(&mut *s);
247
248        for expected in TEST_STR.chars().rev() {
249            let actual = iter.next_back().expect("expected a character ref");
250
251            assert_eq!(actual.len(), expected.len_utf8());
252            assert_eq!(actual.as_char(), expected);
253        }
254
255        assert!(
256            iter.next_back().is_none(),
257            "expected no more character refs"
258        );
259
260        let size_hint = iter.size_hint();
261        assert_eq!(size_hint.0, 0);
262        assert_eq!(size_hint.1, Some(0));
263    }
264
265    #[test]
266    fn test_nth_backwards() {
267        let mut s = test_str_owned();
268
269        for step in 0..4 {
270            let mut iter = CharMutRefs::from(&mut *s);
271            let mut expected_chars = TEST_STR.chars();
272
273            while let Some(expected) = expected_chars.nth_back(step) {
274                let actual = iter.nth_back(step).expect("expected a character ref");
275
276                assert_eq!(actual.len(), expected.len_utf8());
277                assert_eq!(actual.as_char(), expected);
278            }
279
280            assert!(
281                iter.nth_back(step).is_none(),
282                "expected no more character refs"
283            );
284        }
285
286        assert!(CharMutRefs::from(&mut *s).nth_back(4).is_none());
287    }
288
289    #[test]
290    fn test_mut() {
291        let s = "abcd";
292        let mut s2 = Box::<str>::from(s);
293
294        for char in s2.mut_iter() {
295            char.make_ascii_uppercase();
296        }
297
298        assert_eq!(&*s2, s.to_ascii_uppercase());
299    }
300}