Skip to content

Commit eb2fd88

Browse files
committed
FusedIterator marker trait and iter::Fuse specialization
This RFC adds a `FusedIterator` marker trait and specializes `iter::Fuse` to do nothing when the underlying iterator already provides the `Fuse` guarantee.
1 parent 0c1cc7b commit eb2fd88

File tree

1 file changed

+246
-0
lines changed

1 file changed

+246
-0
lines changed

text/0000-fused-iterator.md

+246
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
- Feature Name: fused
2+
- Start Date: 2016-04-15
3+
- RFC PR: (leave this empty)
4+
- Rust Issue: (leave this empty)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Add a marker trait `FusedIterator` to `std::iter` and implement it on `Fuse<I>` and
10+
applicable iterators and adapters. By implementing `FusedIterator`, an iterator
11+
promises to behave as if `Iterator::fuse()` had been called on it (i.e. return
12+
`None` forever after returning `None` once). Then, specialize `Fuse<I>` to be a
13+
no-op iff `I` implements `FusedIterator`.
14+
15+
# Motivation
16+
[motivation]: #motivation
17+
18+
Iterators are allowed to return whatever they want after returning `None` once.
19+
However, assuming that an iterator continues to return `None` can make
20+
implementing some algorithms/adapters easier. Therefore, `Fused` and
21+
`Iterator::fuse` exist. Unfortunately, the `Fused` iterator adapter introduces a
22+
noticeable overhead. Furthermore, many iterators (most if not all iterators in
23+
std) already act as if they were fused (this is considered to be the "polite"
24+
behavior). Therefore, it would be nice to be able to pay the `Fused` overhead
25+
iff necessary.
26+
27+
Microbenchmarks:
28+
29+
```text
30+
test fuse ... bench: 200 ns/iter (+/- 13)
31+
test fuse_fuse ... bench: 250 ns/iter (+/- 10)
32+
test myfuse ... bench: 48 ns/iter (+/- 4)
33+
test myfuse_myfuse ... bench: 48 ns/iter (+/- 3)
34+
test range ... bench: 48 ns/iter (+/- 2)
35+
```
36+
37+
```rust
38+
#![feature(test, specialization)]
39+
extern crate test;
40+
41+
use std::ops::Range;
42+
43+
#[derive(Clone, Debug)]
44+
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
45+
pub struct MyFuse<I> {
46+
iter: I,
47+
done: bool
48+
}
49+
50+
pub trait Fused: Iterator {}
51+
52+
trait IterExt: Iterator + Sized {
53+
fn myfuse(self) -> MyFuse<Self> {
54+
MyFuse {
55+
iter: self,
56+
done: false,
57+
}
58+
}
59+
}
60+
61+
impl<I> Fused for MyFuse<I> where MyFuse<I>: Iterator {}
62+
impl<T> Fused for Range<T> where Range<T>: Iterator {}
63+
64+
impl<T: Iterator> IterExt for T {}
65+
66+
impl<I> Iterator for MyFuse<I> where I: Iterator {
67+
type Item = <I as Iterator>::Item;
68+
69+
#[inline]
70+
default fn next(&mut self) -> Option<<I as Iterator>::Item> {
71+
if self.done {
72+
None
73+
} else {
74+
let next = self.iter.next();
75+
self.done = next.is_none();
76+
next
77+
}
78+
}
79+
}
80+
81+
impl<I> Iterator for MyFuse<I> where I: Iterator + Fused {
82+
#[inline]
83+
fn next(&mut self) -> Option<<I as Iterator>::Item> {
84+
self.iter.next()
85+
}
86+
}
87+
88+
impl<I> ExactSizeIterator for MyFuse<I> where I: ExactSizeIterator {}
89+
90+
#[bench]
91+
fn myfuse(b: &mut test::Bencher) {
92+
b.iter(|| {
93+
for i in (0..100).myfuse() {
94+
test::black_box(i);
95+
}
96+
})
97+
}
98+
99+
#[bench]
100+
fn myfuse_myfuse(b: &mut test::Bencher) {
101+
b.iter(|| {
102+
for i in (0..100).myfuse().myfuse() {
103+
test::black_box(i);
104+
}
105+
});
106+
}
107+
108+
109+
#[bench]
110+
fn fuse(b: &mut test::Bencher) {
111+
b.iter(|| {
112+
for i in (0..100).fuse() {
113+
test::black_box(i);
114+
}
115+
})
116+
}
117+
118+
#[bench]
119+
fn fuse_fuse(b: &mut test::Bencher) {
120+
b.iter(|| {
121+
for i in (0..100).fuse().fuse() {
122+
test::black_box(i);
123+
}
124+
});
125+
}
126+
127+
#[bench]
128+
fn range(b: &mut test::Bencher) {
129+
b.iter(|| {
130+
for i in (0..100) {
131+
test::black_box(i);
132+
}
133+
})
134+
}
135+
```
136+
137+
# Detailed Design
138+
[design]: #detailed-design
139+
140+
```
141+
trait FusedIterator: Iterator {}
142+
143+
impl<I: Iterator> FusedIterator for Fuse<I> {}
144+
145+
impl<A> FusedIterator for Range<A> {}
146+
// ...and for most std/core iterators...
147+
148+
149+
// Existing implementation of Fuse repeated for convenience
150+
pub struct Fuse<I> {
151+
iterator: I,
152+
done: bool,
153+
}
154+
155+
impl<I> Iterator for Fuse<I> where I: Iterator {
156+
type Item = I::Item;
157+
158+
#[inline]
159+
fn next(&mut self) -> Self::Item {
160+
if self.done {
161+
None
162+
} else {
163+
let next = self.iterator.next();
164+
self.done = next.is_none();
165+
next
166+
}
167+
}
168+
}
169+
170+
// Then, specialize Fuse...
171+
impl<I> Iterator for Fuse<I> where I: FusedIterator {
172+
type Item = I::Item;
173+
174+
#[inline]
175+
fn next(&mut self) -> Self::Item {
176+
// Ignore the done flag and pass through.
177+
// Note: this means that the done flag should *never* be exposed to the
178+
// user.
179+
self.iterator.next()
180+
}
181+
}
182+
183+
```
184+
185+
# Drawbacks
186+
[drawbacks]: #drawbacks
187+
188+
1. Yet another special iterator trait.
189+
2. There is a useless done flag on no-op `Fuse` adapters.
190+
3. Fuse isn't used very often anyways. However, I would argue that it should be
191+
used more often and people are just playing fast and loose. I'm hoping that
192+
making `Fuse` free when unneeded will encourage people to use it when they should.
193+
194+
# Alternatives
195+
196+
## Do Nothing
197+
198+
Just pay the overhead on the rare occasions when fused is actually used.
199+
200+
## Associated Type
201+
202+
Use an associated type (and set it to `Self` for iterators that already provide
203+
the fused guarantee) and an `IntoFused` trait:
204+
205+
```rust
206+
#![feature(specialization)]
207+
use std::iter::Fuse;
208+
209+
trait FusedIterator: Iterator {}
210+
211+
trait IntoFused: Iterator + Sized {
212+
type Fused: Iterator<Item = Self::Item>;
213+
fn into_fused(self) -> Self::Fused;
214+
}
215+
216+
impl<T> IntoFused for T where T: Iterator {
217+
default type Fused = Fuse<Self>;
218+
default fn into_fused(self) -> Self::Fused {
219+
// Currently complains about a mismatched type but I think that's a
220+
// specialization bug.
221+
self.fuse()
222+
}
223+
}
224+
225+
impl<T> IntoFused for T where T: FusedIterator {
226+
type Fused = Self;
227+
228+
fn into_fused(self) -> Self::Fused {
229+
self
230+
}
231+
}
232+
```
233+
234+
For now, this doesn't actually compile because rust believes that the associated
235+
type `Fused` could be specialized independent of the `into_fuse` function.
236+
237+
While this method gets rid of memory overhead of a no-op `Fuse` wrapper, it adds
238+
complexity, needs to be implemented as a separate trait (because adding
239+
associated types is a breaking change), and can't be used to optimize the
240+
iterators returned from `Iterator::fuse` (users would *have* to call
241+
`IntoFused::into_fused`).
242+
243+
# Unresolved questions
244+
[unresolved]: #unresolved-questions
245+
246+
None.

0 commit comments

Comments
 (0)