Skip to content

Add a foreach() method to std::iter::Iterator #1064

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions text/0000-foreach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
- Feature Name: foreach
- Start Date: 16 April 2015
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)

# Summary

Add a `foreach()` method to `std::iter::Iterator`, which eagerly calls a function on
each item the Iterator contains and returns `()`.

# Motivation

By design, iterators are lazy, and will not be evaluated until they are consumed
in some way. The design of the `std::iter` library, and the language in general,
strongly encourages chaining Iterator method calls one after another. This idiom
is a very positive attribute of Rust: things that are hard to do by chaining
iterators are usually things that make code less comprehensible (e.g. it is
usually clearer code to `filter()` than to `continue` and to `take()` than to
`break`).

The `foreach()` method proposed by this RFC is fundamentally sugar; it does not
enable programmers to write any instruction not already possible with existing
language features. The most common recommendation is to write a `for` loop, but
it is also possible to chain one of several consuming iterator methods after a
map, to write a fold which returns `()`, or to add a dependency to the itertools
crate and use the foreach method defined there. A
[prior RFC](https://github.com/rust-lang/rfcs/pull/582) to the same effect was
closed without merging for these reasons.

However, if the inclusion of `foreach()` is syntactic sugar, then the exclusion of
`foreach()` is syntactic salt. Several consumers are currently 'blessed' by std as
special methods of Iterator, but no generic consumer method exists. For this
reason, anyone who needs to consume an iterator by some mean not blessed by std
needs to either depend on a crate whose tools are otherwise _more specific_,
rather than _more general_, or do something that feels unidiomatic and wrong.
Compare these three examples (the first being semantically different from the
others, and the last being enabled by this RFC):

```rust
let vec = my_collection.iter()
.filter_map(|x| { ... })
.take_while(|x| { ... })
.collect::<Vec<_>>()
```
```rust
let tmp = my_collection.iter()
.filter_map(|x| { ... })
.take_while(|x| { ... });
for x in tmp {
tx.send(x).ok();
}
```
```rust
my_collection.iter()
.filter_map(|x| { ... })
.take_while(|x| { ... })
.foreach(|x| { tx.send(x).ok(); });
```

Surely it is not intended that consuming an iterator by sending each element
across a thread boundary (or by any other means not blessed by std) seem
unidiomatic, but that is the effect of the current design. This is not about
making std 'batteries included', it is about defining the limits of idiomatic
Rust.

# Detailed design

Implementing this RFC is quite simple. Since this is, in implementation, just
sugar, it involves adding a very small predefined method to the
`std::iter::Iterator` trait, which would look something like this:

```
fn foreach<F>(&mut self, mut f: F) where F: FnMut(Self::Item) {
for item in self { f(item); }
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should take &mut self then it can be used on A) Iterators that can't be moved out from B) Iterator trait objects. Same functionality.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last I checked, the idea was for consuming iterator to take by value for convenience, since the by-ref behavior can be recovered with the by_ref() adapter

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for trait objects with manual cast to &mut Iterator rather than .by_ref() in that case.

What is the convenience difference? I think it should be callable everywhere the self version is.

```

# Drawbacks

The main reason not to add this method is that it is sugar.

It could be argued that the Itertools crate implements `foreach()`, and that the
Itertools crate is not very popular, making this uncommonly used sugar. To this
there are several responses:
* The common way to get around this salt is probably not to add a dependency,
but to do the salty thing and write a `for` loop or add a `.all(|_| true)` and
so on. Thus, the popularity of this method cannot be found by analyzing the
popularity of the Itertools crate.
* Even if this method were not popularly used (because the blessed consumers
satisfy the majority of cases), that does not mean that a flexible method
for consuming an iterator should not be made available by std; even if its
use is a corner case, the blessed consumers are all degenerate
implementations of foreach and the only reason to make available degenerate
cases without the general case is if the general case is intended to be
unidiomatic.

# Alternatives

### Do nothing

The main alternative is to do nothing.

### Implement `each()` or some other method with similar but distinct semantics.

Alternatively, Rust could have a similar method with different semantics, such
as a method like Ruby's `each()`, which is essentially an eager version of Rust's
`inspect`. I think maintaining the consistency of iterators return iterators
being lazy is valuable, and that this method should return `()`.

# Unresolved questions

Should this method be named `foreach()` or `for_each()`? I think of __foreach__ as
an existing PL keyword (used mainly by languages with non-iterative __for__
loops), so my preference is foreach(), but it doesn't much matter to me. It also
could be called `each()`, like Ruby's method, though its semantics are different.