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

Introduce the IntoIterator trait and reimplement for-loops to use it #20790

Merged
merged 17 commits into from
Jan 31, 2015

Conversation

japaric
Copy link
Member

@japaric japaric commented Jan 9, 2015

As per RFC #235, you can now do:

let mut v = vec![1];

// iterate over immutable references
for x in &v {
    assert_eq!(x, &1);
}

// iterate over mutable references
for x in &mut v {
    assert_eq!(x, &mut 1);
}

// iterate over values, this consumes `v`
for x in v {
    assert_eq!(x, 1);
}

[breaking-change]s

For loops now "consume" (move) the iterator, this breaks iterating over mutable references to iterators, and also breaks multiple iterations over the same iterator:

fn foo(mut it: &mut Iter) {  // `Iter` implements `Iterator`
    for x in it { .. }  //~ error: `&mut Iter` doesn't implement Iterator
}

fn bar() {
    for x in it { .. }  //~ note: `it` moved here
    for x in it { .. }  //~ error: `it` has been moved
}

Both cases can be fixed using the by_ref() adapter to create an iterator from the mutable reference:

fn foo(mut it: &mut Iter) {
    for x in it.by_ref() { .. }
}

fn bar() {
    for x in it.by_ref() { .. }
    for x in it { .. }
}

This PR also makes iterator non-implicitly copyable, as this was source of subtle bugs in the libraries. You can still use clone() to explictly copy the iterator.

Finally, since the for loops are implemented in the frontend and use global paths to IntoIterator, Iterator and Option variants, users of the core crate will have to use add an std module to the root of their crate to be able to use for loops:

#![no_std]

extern crate core;

fn main() {
    for x in 0..10 {}
}

#[doc(hidden)]
mod std {
    // these imports are needed to use for-loops
    pub use core::iter;
    pub use core::option;
}

r? @nikomatsakis @aturon
cc #18424
closes #18045

@rust-highfive
Copy link
Collaborator

r? @alexcrichton

(rust_highfive has picked a reviewer for you, use r? to override)

@huonw
Copy link
Member

huonw commented Jan 9, 2015

Can we do this without expanding in the front end?

@japaric
Copy link
Member Author

japaric commented Jan 9, 2015

@huonw @nikomatsakis was interested in an implementation that used the frontend (hence this prototype)

@huonw
Copy link
Member

huonw commented Jan 9, 2015

Also, expanding to include _ => break will mean that refutable patterns like for 1 in iter will "work", which seems like something that we don't want to allow.

@japaric
Copy link
Member Author

japaric commented Jan 9, 2015

(I think @aturon and @nikomatsakis mainly want to measure the size of the fallout and the change in ergonomics of these new for-loop semantics before commiting to it. This implementation is minimal work to measure that. We can come up with a better implementation if we decide to actually do this.)

@@ -273,7 +273,7 @@ impl<'a> Iterator for Recompositions<'a> {
loop {
match self.state {
Composing => {
for ch in self.iter {
while let Some(ch) = self.iter.next() {
Copy link
Member

Choose a reason for hiding this comment

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

Could some loops like this be altered to:

for ch in self.iter.by_ref() { /* ... */ }

That may avoid consuming the entire iterator.

Copy link
Member

Choose a reason for hiding this comment

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

Actually, if it helps in basically all cases, I wonder if we could spec it like this...

Copy link
Member Author

Choose a reason for hiding this comment

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

I had forgotten about by_ref()! I think we can use that instead of most (all?) the while lets.

Actually, if it helps in basically all cases, I wonder if we could spec it like this...

You mean using by_ref() in the for loop expansion?

@japaric
Copy link
Member Author

japaric commented Jan 10, 2015

Updated to use the by_ref() adapter instead of while let.

@nikomatsakis
Copy link
Contributor

Some thoughts:

  1. As I mentioned to @japaric on IRC, I think we can expand to match <iterator> { mut iter => ... } instead of { let mut iter = <iterator>; .. } to resolve the matter of temporary lifetimes.
  2. Personally, I prefer to do this expansion than to have for loops in the AST that typeck/borrowck/everybody-else has to deal with. Basically desugaring makes the rest of the compiler simpler. However, the catch would be if it negatively affects error messages and so forth. So I'm curious @japaric as to the impact there. There is also the problem of our inability to really name things in std reliably, though this is ameliorated by the fact that #![no_std] is not stable yet.

@jroesch
Copy link
Member

jroesch commented Jan 10, 2015

@japaric and @nikomatsakis As a fan of desugaring as much as possible I think this seems like a good idea. If error messages become a problem you could use a similar scheme to what they do with Pyret where you provide a tag (either in some table or directly on the AST) for which rewrite rule you used, and then utilize that information when reporting errors by re-sugaring it then pretty printing. I would be happy to help explore such a scheme if the error reporting suffers.

@japaric
Copy link
Member Author

japaric commented Jan 12, 2015

Updated PR with @nikomatsakis suggestions (match IntoIterator + UFCS with absolute paths, see expansion in the top comment), which got rid of the temporary lifetime issue, reduced the size of the import fallout and removed the need to have IntoIterator in the prelude. I'm now blocked on a type inference bug, but @nikomatsakis is aware of it.

I've collected some error messages from 3 cfail tests related to for-loops:

  • for-loop-bogosity.rs
struct MyStruct {
    x: isize,
    y: isize,
}

impl MyStruct {
    fn next(&mut self) -> Option<isize> {
        Option::Some(self.x)
    }
}

fn main() {
    let mut bogus = MyStruct {
        x: 1,
        y: 2,
    };
    for x in bogus {
//~^  old-ERROR has type `MyStruct` which does not implement the `Iterator` trait
//~^^ new-ERROR the trait `iter::Iterator` is not implemented for the type `MyStruct`
    }
}

The error message changed its wording but the meaning is the same.

  • for-loop-refutable-pattern-error-message.rs
fn main() {
    for
        &1i8
//~^  old-ERROR refutable pattern in `for` loop binding
//~^^ new-ERROR non-exhaustive patterns: `Some(&_)` not covered
        in [1i8].iter() {}
}

The message is more obscure now.

  • for-loop-type-error.rs
fn main() {
    let x = () + (); //~ ERROR binary operation

    // this shouldn't have a flow-on error:
    // japaric: and it doesn't with the either version of for loops
    for _ in x {}
}

Behavior remains the same.

@@ -65,6 +65,8 @@
#![allow(unknown_features)] #![feature(int_uint)]
#![feature(on_unimplemented)]
#![deny(missing_docs)]
// SNAP 9e4e524 remove `allow(unused_mut)` after a snapshot
#![allow(unused_mut)]
Copy link
Member

Choose a reason for hiding this comment

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

Now that we have cfg_attr, could this be #![cfg_attr(stage0, allow(unused_mut))]? Anything with the string "stage0" in it is a pretty easy flag for something to clean out when dealing with snapshots.

@nikomatsakis
Copy link
Contributor

@japaric and I were talking on IRC. My feeling is that the error in this example:

fn main() {
    for
        &1i8
//~^  old-ERROR refutable pattern in `for` loop binding
//~^^ new-ERROR non-exhaustive patterns: `Some(&_)` not covered
        in [1i8].iter() {}
}

is actually better. I doubt most "lay people" know what a "refutable pattern" is. (I personally have to work out from first principles which is "irrefutable" and which is "refutable".) But having a concrete counter example seems immediately understandable. It might be better still to combine the two messages:

"refutable pattern in `for` loop binding: `Some(&_)` not covered"

@huonw
Copy link
Member

huonw commented Jan 13, 2015

@nikomatsakis I think introducing the Some out of nowhere is confusing (and is not very useful since it happens for all invalid iterators, and is not related to how one might fix it, in the common case).

@japaric japaric changed the title [proto][WIP] Introduce the IntoIterator trait and reimplement for-loops to use it Introduce the IntoIterator trait and reimplement for-loops to use it Jan 23, 2015
@japaric
Copy link
Member Author

japaric commented Jan 23, 2015

This PR is ready! (passed check-stage1, running full make check now)

re: error message on refutable patterns, I decide to reuse the old message/diagnostic code, we can customize it later if desired.

@japaric
Copy link
Member Author

japaric commented Jan 26, 2015

rebased and added tests for issues that new for loops fix. Also fixed a recursive call that the new unconditional_recursion lint pointed out. (last 3 commits are new)

@japaric
Copy link
Member Author

japaric commented Jan 26, 2015

Just a heads up, until #21637 gets fixed, we won't be able to use the IntoIterator trait to build iterator-centric APIs. Code like this one ICEs right now:

fn concat<I: IntoIterator>(it: I) -> String where <I::Iter as Iterator>::Item: Str {
    unimplemented!();
}

@@ -0,0 +1,7 @@
std
Copy link
Member

Choose a reason for hiding this comment

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

oops!

Copy link
Member Author

Choose a reason for hiding this comment

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

ugh..
/me snipes file

alexcrichton added a commit to alexcrichton/rust that referenced this pull request Jan 30, 2015
As per [RFC rust-lang#235][rfc], you can now do:

[rfc]: https://github.com/rust-lang/rfcs/blob/master/text/0235-collections-conventions.md#intoiterator-and-iterable

``` rust
let mut v = vec![1];

// iterate over immutable references
for x in &v {
    assert_eq!(x, &1);
}

// iterate over mutable references
for x in &mut v {
    assert_eq!(x, &mut 1);
}

// iterate over values, this consumes `v`
for x in v {
    assert_eq!(x, 1);
}
```

[breaking-change]s

For loops now "consume" (move) the iterator, this breaks iterating over mutable references to iterators, and also breaks multiple iterations over the same iterator:

``` rust
fn foo(mut it: &mut Iter) {  // `Iter` implements `Iterator`
    for x in it { .. }  //~ error: `&mut Iter` doesn't implement Iterator
}

fn bar() {
    for x in it { .. }  //~ note: `it` moved here
    for x in it { .. }  //~ error: `it` has been moved
}
```

Both cases can be fixed using the `by_ref()` adapter to create an iterator from the mutable reference:

``` rust
fn foo(mut it: &mut Iter) {
    for x in it.by_ref() { .. }
}

fn bar() {
    for x in it.by_ref() { .. }
    for x in it { .. }
}
```

This PR also makes iterator non-implicitly copyable, as this was source of subtle bugs in the libraries. You can still use `clone()` to explictly copy the iterator.

Finally, since the for loops are implemented in the frontend and use global paths to `IntoIterator`, `Iterator` and `Option` variants, users of the `core` crate will have to use add an `std` module to the root of their crate to be able to use for loops:

``` rust
#![no_std]

extern crate core;

fn main() {
    for x in 0..10 {}
}

#[doc(hidden)]
mod std {
    // these imports are needed to use for-loops
    pub use core::iter;
    pub use core::option;
}
```

---

r? @nikomatsakis @aturon
cc rust-lang#18424
closes rust-lang#18045
@bors
Copy link
Contributor

bors commented Jan 30, 2015

⌛ Testing commit b9a9030 with merge e47284b...

@alexcrichton
Copy link
Member

@bors: retry

@bors
Copy link
Contributor

bors commented Jan 31, 2015

⌛ Testing commit b9a9030 with merge e513946...

@bors
Copy link
Contributor

bors commented Jan 31, 2015

💔 Test failed - auto-linux-64-opt

@alexcrichton
Copy link
Member

@bors: retry

@bors
Copy link
Contributor

bors commented Jan 31, 2015

⌛ Testing commit b9a9030 with merge 581a5c8...

@bors
Copy link
Contributor

bors commented Jan 31, 2015

💔 Test failed - auto-linux-64-x-android-t

@alexcrichton
Copy link
Member

@bors: retry

@bors
Copy link
Contributor

bors commented Jan 31, 2015

⌛ Testing commit b9a9030 with merge 692c620...

@bors
Copy link
Contributor

bors commented Jan 31, 2015

💔 Test failed - auto-linux-32-opt

@alexcrichton
Copy link
Member

@bors: retry

@bors
Copy link
Contributor

bors commented Jan 31, 2015

⌛ Testing commit b9a9030 with merge 72eab3e...

@alexcrichton alexcrichton merged commit b9a9030 into rust-lang:master Jan 31, 2015
@japaric japaric deleted the for-loops branch January 31, 2015 13:29
bors added a commit that referenced this pull request Feb 1, 2015
Removes `Copy` from `ops::Range` (`a..b`) and `ops::RangeFrom` (`a..`)

[breaking-change]

---

I forgot about these two in #20790, this PR also adds `Clone` to the `Peekable` adapter which used to be `Copy`able.

r? @nikomatsakis or anyone
@taralx
Copy link
Contributor

taralx commented Feb 3, 2015

The RFC didn't mention this "mod std" business, and I'm a bit uncomfortable with it. Why not directly access the core crate?

@japaric
Copy link
Member Author

japaric commented Feb 3, 2015

Why not directly access the core crate?

If you mean expanding to ::core::iter::IntoIterator, that won't work in most cases, since most crates are linked to std but not to core (they have access to core stuff because of re-exports).

The RFC didn't mention this "mod std" business, and I'm a bit uncomfortable with it.

You already have to use the mod std to use the #[derive] syntax extension and/or slicing syntax in #![no_std] crates. At the moment this required for stuff that's implemented in the frontend, but we'd definitely like to improve this situation in the future.

milibopp added a commit to milibopp/rust-itertools that referenced this pull request Feb 3, 2015
@japaric
Copy link
Member Author

japaric commented Feb 3, 2015

@taralx see #21912 ;-)

@alexchandel
Copy link

The relevant section of the Rust Reference ought to be updated to reflect this, cc @steveklabnik

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implicitly copyable iterators are mega confusing