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}