Range Over Iterators in Mercury

Starting with version 1.23, the Go programming language has added support for iterators, which lets us range over pretty much anything!

Let’s look at the List type from a previous example. In that example, we had an AllElements method that returned a slice of all elements in the list. With Go iterators, we can do it better - as shown below.

type List<T> = Option<Box<Element<T>>>;

#[derive(Clone)]
struct Element<T> {
    next: List<T>,
    val: T,
}

impl<T> List<T> {
    fn push(&mut self, v: T) {
        let new_element = Some(Box::new(Element {
            next: self.take(),
            val: v,
        }));
        *self = new_element;
    }

    fn all(&self) -> impl Iterator<Item = &T> {
        struct ListIter<'a, T> {
            current: &'a List<T>,
        }

        impl<'a, T> Iterator for ListIter<'a, T> {
            type Item = &'a T;

            fn next(&mut self) -> Option<Self::Item> {
                match self.current {
                    Some(element) => {
                        self.current = &element.next;
                        Some(&element.val)
                    }
                    None => None,
                }
            }
        }

        ListIter { current: self }
    }
}

fn gen_fib() -> impl Iterator<Item = i32> {
    struct Fib {
        a: i32,
        b: i32,
    }

    impl Iterator for Fib {
        type Item = i32;

        fn next(&mut self) -> Option<Self::Item> {
            let next_value = self.a;
            self.a = self.b;
            self.b = next_value + self.b;
            Some(next_value)
        }
    }

    Fib { a: 1, b: 1 }
}

fn main() {
    let mut lst: List<i32> = None;
    lst.push(23);
    lst.push(13);
    lst.push(10);

    // Since List.all returns an iterator, we can use it in a regular for loop.
    for e in lst.all() {
        println!("{}", e);
    }

    let all: Vec<_> = lst.all().collect();
    println!("all: {:?}", all);

    for n in gen_fib() {
        if n >= 10 {
            break;
        }
        println!("{}", n);
    }
}

To run the program, ensure you have Rust installed and create a cargo project with this code in the main.rs file. Then run cargo run to execute it.

$ cargo run

The expected output should be:

10
13
23
all: [10, 13, 23]
1
1
2
3
5
8

Packages like std::iter have a number of useful functions to work with iterators. For example, collect takes any iterator and collects all its values into a vector.

Once the loop hits break or an early return, the iterator will stop producing values.