On Mar 4, 2014, at 5:23 AM, Tommi <rusty.ga...@icloud.com> wrote:

> I agree with the spirit of your proposal. But I would change that first 
> clause above to read:
> 
> An iterator is said to be "well-behaved" when its .next() method always 
> returns None if the iterator logically has no elements to iterate over.
> 
> And all iterators should, by convention, be well-behaved. Otherwise it's 
> impossible to pinpoint what exactly is the bug in the following code:
> 
> struct Digits {
>     n: int
> }
> 
> impl Iterator<int> for Digits {
>     fn next(&mut self) -> Option<int> {
>         self.n += 1;
> 
>         if self.n == 10 {
>             None
>         }
>         else {
>             Some(self.n)
>         }
>     }
> }
> 
> fn main() {
>     let mut itr = Digits { n: -1 };
> 
>     for i in itr { // for-loop consumes all items in itr
>         println!("{}", i);
>     }
> 
>     let sum = itr.fold(0, |a, b| a + b); // Infinite loop
>     println!("{}", sum);
> }
> 
> Given the current std::iter::Iterator specification [1], the implementation 
> of the .next() method of Digits is valid. Also, the fold method of Iterator 
> trait should return the initial state (the first argument) when fold is 
> called on an empty iterator, but the call get's stuck on an infinite loop 
> instead.

The bug is pretty obvious. You're using an iterator after it's been exhausted. 
This means you're relying on behavior specific to that implementation of the 
iterator.

One reason why the iterator protocol allows this is precisely to allow things 
like an iterator that yields multiple distinct sequences. And that's what your 
Digits iterator is. It yields two sequences, the first is the finite sequence 
[1, 10), and the second is the infinite sequence [11, ...]. So the fold() runs 
forever because the second sequence is infinite. If Digits only ever yielded a 
single infinite sequence [0, ...] then your fold() would still run forever. 
Alternatively, if your Digits was implemented to return multiple finite 
sequences, your fold() would work. For example

impl Iterator<int> for Digits {
    fn next(&mut self) -> Option<int> {
        self.n += 1;
        if self.n % 10 == 0 {
            None
        } else {
            Some(self.n)
        }
    }
}

This variant yields successive 9-element sequences, skipping every value 
divisible by 10.

If you do need to touch an iterator after it's been exhausted, and you want the 
post-exhaustion behavior to be always returning None, that's what .fuse() is 
for.

fn main() {
    let mut itr = Digits { n: -1 }.fuse();
    
    for i in itr {
        println!("{}", i);
    }
    
    let sum = itr.fold(0, |a, b| a + b);
    println!("{}", sum);
}

But of course this still has a bug, which is that the fold is now guaranteed to 
return 0, because you exhausted the iterator already.

-Kevin

Attachment: smime.p7s
Description: S/MIME cryptographic signature

_______________________________________________
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to