Skip to content

Commit 22f7267

Browse files
committed
Use RLE as a stronger motivating example
The motivating example we had given for `gen` blocks admitted too easy an implementation with existing stable iterator combinators. Let's make the example more *motivating* by showing a simple algorithm, run-length encoding, that's more difficult to implement in other ways. (Thanks to Ralf Jung for pointing out the need for a better example.)
1 parent 354abf6 commit 22f7267

File tree

1 file changed

+87
-32
lines changed

1 file changed

+87
-32
lines changed

text/3513-gen-blocks.md

Lines changed: 87 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,53 +13,108 @@ This RFC reserves the `gen` keyword in the Rust 2024 edition for generators and
1313

1414
Writing iterators manually can be painful. Many iterators can be written by chaining together iterator combinators, but some need to be written with a manual implementation of `Iterator`. This can push people to avoid iterators and do worse things such as writing loops that eagerly store values to mutable state. With `gen` blocks, we can now write a simple `for` loop and still get a lazy iterator of values.
1515

16-
By way of example, consider these ways of expressing the same function:
16+
By way of example, consider these alternate ways of expressing [run-length encoding][]:
1717

18-
```rust
19-
// This example uses iterator combinators.
20-
fn odd_dup(xs: impl IntoIterator<Item = u32>) -> impl Iterator<Item = u32> {
21-
xs.into_iter().filter(|x| x.is_odd()).map(|x| x * 2)
22-
}
18+
[run-length encoding]: https://en.wikipedia.org/wiki/Run-length_encoding
2319

24-
// This example uses `iter::from_fn`.
25-
fn odd_dup(xs: impl IntoIterator<Item = u32>) -> impl Iterator<Item = u32> {
26-
let mut xs = xs.into_iter();
27-
std::iter::from_fn(move || {
28-
while let Some(x) = xs.next() {
29-
if x.is_odd() {
30-
return Some(x * 2);
20+
```rust
21+
// This example uses `gen` blocks, introduced in this RFC.
22+
fn rl_encode<I: IntoIterator<Item = u8>>(
23+
xs: I,
24+
) -> impl Iterator<Item = u8> {
25+
gen {
26+
let mut xs = xs.into_iter();
27+
let (Some(mut cur), mut n) = (xs.next(), 0) else { return };
28+
for x in xs {
29+
if x == cur && n < u8::MAX {
30+
n += 1;
31+
} else {
32+
yield n; yield cur;
33+
(cur, n) = (x, 0);
3134
}
3235
}
33-
None
34-
})
36+
yield n; yield cur;
37+
}.into_iter()
3538
}
3639

3740
// This example uses a manual implementation of `Iterator`.
38-
fn odd_dup(xs: impl IntoIterator<Item = u32>) -> impl Iterator<Item = u32> {
39-
struct OddDup<T>(T);
40-
impl<T: Iterator<Item = u32>> Iterator for OddDup<T> {
41-
type Item = u32;
42-
fn next(&mut self) -> Option<u32> {
43-
while let Some(x) = self.0.next() {
44-
if x.is_odd() {
45-
return Some(x * 2)
41+
fn rl_encode<I: IntoIterator<Item = u8>>(
42+
xs: I,
43+
) -> impl Iterator<Item = u8> {
44+
struct RlEncode<I: IntoIterator<Item = u8>> {
45+
into_xs: Option<I>,
46+
xs: Option<<I as IntoIterator>::IntoIter>,
47+
cur: Option<<I as IntoIterator>::Item>,
48+
n: u8,
49+
yield_x: Option<<I as IntoIterator>::Item>,
50+
}
51+
impl<I: IntoIterator<Item = u8>> Iterator for RlEncode<I> {
52+
type Item = u8;
53+
fn next(&mut self) -> Option<Self::Item> {
54+
let xs = self.xs.get_or_insert_with(|| unsafe {
55+
self.into_xs.take().unwrap_unchecked().into_iter()
56+
});
57+
if let Some(x) = self.yield_x.take() {
58+
return Some(x);
59+
}
60+
loop {
61+
match (xs.next(), self.cur) {
62+
(Some(x), Some(cx))
63+
if x == cx && self.n < u8::MAX => self.n += 1,
64+
(Some(x), Some(cx)) => {
65+
let n_ = self.n;
66+
(self.cur, self.n) = (Some(x), 0);
67+
self.yield_x = Some(cx);
68+
return Some(n_);
69+
}
70+
(Some(x), None) => {
71+
(self.cur, self.n) = (Some(x), 0);
72+
}
73+
(None, Some(cx)) => {
74+
self.cur = None;
75+
self.yield_x = Some(cx);
76+
return Some(self.n);
77+
}
78+
(None, None) => return None,
4679
}
4780
}
48-
None
4981
}
5082
}
51-
OddDup(xs.into_iter())
83+
RlEncode {
84+
into_xs: Some(xs), xs: None, cur: None, n: 0, yield_x: None,
85+
}
5286
}
5387

54-
// This example uses `gen` blocks, introduced in this RFC.
55-
fn odd_dup(xs: impl IntoIterator<Item = u32>) -> impl Iterator<Item = u32> {
56-
gen {
57-
for x in xs {
58-
if x.is_odd() {
59-
yield x * 2;
88+
// This example uses `iter::from_fn`.
89+
fn rl_encode<I: IntoIterator<Item = u8>>(
90+
xs: I,
91+
) -> impl Iterator<Item = u8> {
92+
let (mut cur, mut n, mut yield_x) = (None, 0, None);
93+
let (mut into_xs, mut xs) = (Some(xs), None);
94+
core::iter::from_fn(move || loop {
95+
let xs = xs.get_or_insert_with(|| unsafe {
96+
into_xs.take().unwrap_unchecked().into_iter()
97+
});
98+
if let Some(x) = yield_x.take() {
99+
return Some(x);
100+
}
101+
match (xs.next(), cur) {
102+
(Some(x), Some(cx)) if x == cx && n < u8::MAX => n += 1,
103+
(Some(x), Some(cx)) => {
104+
let n_ = n;
105+
(cur, n) = (Some(x), 0);
106+
yield_x = Some(cx);
107+
return Some(n_);
108+
}
109+
(Some(x), None) => (cur, n) = (Some(x), 0),
110+
(None, Some(cx)) => {
111+
cur = None;
112+
yield_x = Some(cx);
113+
return Some(n);
60114
}
115+
(None, None) => return None,
61116
}
62-
}.into_iter()
117+
})
63118
}
64119
```
65120

0 commit comments

Comments
 (0)