Skip to content
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

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

Closed
wants to merge 3 commits into from
Closed
Changes from 2 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);
}
```
```rust
my_collection.iter()
.filter_map(|x| { ... })
.take_while(|x| { ... })
.foreach(|x| { tx.send(); });

Choose a reason for hiding this comment

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

nit: tx.send(x)

Choose a reason for hiding this comment

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

I'm referring to the argument to the function.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I'm sorry.

```

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>(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.