fuzed_iterator/
lib.rs

1// Copyright 2023 WATANABE Yuki
2// Licensed under the Apache License and the MIT License. Users may choose
3// either license (or both), at their option.
4//
5// ----------------------------------------------------------------------------
6//
7// Licensed under the Apache License, Version 2.0 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the License at
10//
11//     http://www.apache.org/licenses/LICENSE-2.0
12//
13// Unless required by applicable law or agreed to in writing, software
14// distributed under the License is distributed on an "AS IS" BASIS,
15// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16// See the License for the specific language governing permissions and
17// limitations under the License.
18//
19// ----------------------------------------------------------------------------
20//
21// Permission is hereby granted, free of charge, to any person obtaining a copy
22// of this software and associated documentation files (the "Software"), to deal
23// in the Software without restriction, including without limitation the rights
24// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25// copies of the Software, and to permit persons to whom the Software is
26// furnished to do so, subject to the following conditions:
27//
28// The above copyright notice and this permission notice shall be included in all
29// copies or substantial portions of the Software.
30//
31// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37// SOFTWARE.
38
39#![no_std]
40#![doc = include_str!("../README.md")]
41#![warn(missing_docs)]
42
43/// Iterator wrapper that panics if `next` is called after it returns `None`
44///
45/// See the [crate-level documentation](self) for more.
46#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
47pub struct Fuze<I> {
48    inner: Option<I>,
49}
50
51impl<I: Default> Default for Fuze<I> {
52    fn default() -> Self {
53        Self {
54            inner: Some(Default::default()),
55        }
56    }
57}
58
59impl<I> From<I> for Fuze<I> {
60    fn from(iter: I) -> Self {
61        Fuze { inner: Some(iter) }
62    }
63}
64
65impl<I> Iterator for Fuze<I>
66where
67    I: Iterator,
68{
69    type Item = I::Item;
70
71    /// Returns the next element of the underlying iterator.
72    ///
73    /// This method drops the underlying iterator once it returns `None`.
74    /// If `next` is called after that, it panics.
75    fn next(&mut self) -> Option<Self::Item> {
76        let inner = self
77            .inner
78            .as_mut()
79            .expect("called `Fuze::next` after it returned `None`");
80        let item = inner.next();
81        if item.is_none() {
82            self.inner = None;
83        }
84        item
85    }
86
87    /// Returns the lower and upper bound on the remaining length of the iterator.
88    ///
89    /// This method delegates to the underlying iterator's `size_hint` method
90    /// if it is available.
91    /// After `Fuze::next` returns `None`, it returns `(0, None)`.
92    fn size_hint(&self) -> (usize, Option<usize>) {
93        self.inner
94            .as_ref()
95            .map_or((0, None), |inner| inner.size_hint())
96    }
97}
98
99impl<I> DoubleEndedIterator for Fuze<I>
100where
101    I: DoubleEndedIterator,
102{
103    /// Returns the next element of the underlying iterator.
104    ///
105    /// This method drops the underlying iterator once it returns `None`.
106    /// If `next_back` is called after that, it panics.
107    fn next_back(&mut self) -> Option<Self::Item> {
108        let inner = self
109            .inner
110            .as_mut()
111            .expect("called `Fuze::next_back` after it returned `None`");
112        let item = inner.next_back();
113        if item.is_none() {
114            self.inner = None;
115        }
116        item
117    }
118}
119
120/// An extension trait that adds the `fuze` method to iterators
121pub trait IteratorExt {
122    /// Converts `self` into a `Fuze`.
123    fn fuze(self) -> Fuze<Self>
124    where
125        Self: Sized;
126}
127
128impl<I: Iterator> IteratorExt for I {
129    fn fuze(self) -> Fuze<Self> {
130        self.into()
131    }
132}
133
134#[cfg(test)]
135mod iterator_tests {
136    use super::*;
137
138    #[test]
139    fn no_panic_until_first_none() {
140        let mut i = "foo".chars().fuze();
141        assert_eq!(i.next(), Some('f'));
142        assert_eq!(i.next(), Some('o'));
143        assert_eq!(i.next(), Some('o'));
144        assert_eq!(i.next(), None);
145    }
146
147    #[test]
148    #[should_panic = "called `Fuze::next` after it returned `None`"]
149    fn panic_on_another_next_after_none() {
150        let mut i = "foo".chars().fuze();
151        i.by_ref().for_each(drop);
152        i.next();
153    }
154
155    #[test]
156    fn no_panic_if_fused() {
157        let mut i = "foo".chars().fuze().fuse();
158        i.by_ref().for_each(drop);
159        assert_eq!(i.next(), None);
160    }
161
162    #[test]
163    fn size_hint() {
164        let mut i = [1, 2, 3].iter().fuze();
165        assert_eq!(i.size_hint(), (3, Some(3)));
166        i.next();
167        assert_eq!(i.size_hint(), (2, Some(2)));
168        i.next();
169        assert_eq!(i.size_hint(), (1, Some(1)));
170        i.next();
171        assert_eq!(i.size_hint(), (0, Some(0)));
172        i.next();
173        assert_eq!(i.size_hint(), (0, None));
174    }
175}
176
177#[cfg(test)]
178mod double_ended_iterator_tests {
179    use super::*;
180
181    #[test]
182    fn no_panic_until_first_none() {
183        let mut i = "foo".chars().fuze();
184        assert_eq!(i.next_back(), Some('o'));
185        assert_eq!(i.next_back(), Some('o'));
186        assert_eq!(i.next_back(), Some('f'));
187        assert_eq!(i.next_back(), None);
188    }
189
190    #[test]
191    #[should_panic = "called `Fuze::next_back` after it returned `None`"]
192    fn panic_on_another_next_back_after_none() {
193        let mut i = "foo".chars().fuze();
194        i.by_ref().rev().for_each(drop);
195        i.next_back();
196    }
197
198    #[test]
199    fn no_panic_if_fused() {
200        let mut i = "foo".chars().fuze().fuse();
201        i.by_ref().rev().for_each(drop);
202        assert_eq!(i.next_back(), None);
203    }
204}