[code improvment] how to compose adapters chains

Hi all,

I'm relatively new to Rust, but have some experiences in code with other PLs

Anyway, my question is quite simple

say I have a iterator and have made many massages on it

let it = get_an_iterator()
it
.inspect(...)
.filter_map(...)
.filter_map(...)
.for_each(...)

Now I want to comose adapters on this iterator to better group my intentions
like

let it = get_an_iterator()
it
.log()
.process()
.run()

how to do it in Rust?
functions and marcos can make it, but I hope I can use it like methods on instance

Thanks in advance

If I've understood correctly, you want to implement new methods on anything that implements Iterator. The way to do that is via the extension trait pattern:

trait IteratorExt : Iterator {
    fn foo(&self);
}

impl<T : Iterator> IteratorExt for T {
    fn foo(&self) {
        // ...
    }
}
2 Likes

If it's meant to support chains too, then it needs to be a two-step process:

fn foo(self) -> MyIteratorWrapper<T>

and then implement all of the iteration logic in Iterator for MyIteratorWrapper<T>

if it's end of the chain, like sum(), you still need foo(self), because you need ability to call self.next() and destroy the iterator.

2 Likes

Shouldn't it just consume the iterator in this case?

It should, indeed!

If you want to build that logic as a chain of the standard adapters, you can replace MyIteratorWrapper<T> with whatever complicated type the chain returns. Unfortunately, closure types can't be named like this, so you'll need to use regular functions instead.

If you're on the nightly compiler, the type_alias_impl_trait feature makes this a bit cleaner:

#![feature(type_alias_impl_trait)]
type OutType = ...;
type InType = ...;

trait IteratorExt : Iterator {
    type FooIter: Iterator<Item = OutType>;
    fn foo(self)->Self::FooIter;
}

impl<T : Iterator<Item = InType>> IteratorExt for T {
    type FooIter = impl Iterator<Item = OutType>;
    fn foo(self)->Self::FooIter {
        self.filter_map(|x| ...)
    }
}

For reusing other iterator chains with closures you can also use Box<dyn Iterator> on stable, or use a free-standing function (not trait method) that returns impl Iterator.

1 Like

I really appreciate all of your helpful replys

Based on your suggestion, I made a small example:

trait IterLogToFS: Iterator {
    fn log(self) -> LoggerToFS<Self>
    where
        Self: Sized,
    {
        LoggerToFS::new(self)
    }
}

struct LoggerToFS<T: Iterator> {
    inner_iter: T,
    idx: usize,
}

impl<T> LoggerToFS<T>
where
    T: Iterator,
{
    fn new(iter: T) -> Self {
        Self {
            inner_iter: iter,
            idx: 0,
        }
    }
}

impl<T> Iterator for LoggerToFS<T>
where
    T: Iterator,
{
    type Item = T::Item;

    fn next(&mut self) -> Option<Self::Item> {
        let rlt = match self.inner_iter.next() {
            None => None,
            Some(v) => Some(v),
        };

        println!("logged item is: {} - {:?}", self.idx, rlt);
        rlt
    }
}

This is basically good and seems to be the standard approach of std::iter

Also, it has ability to be flexible and composable
I can impl a lot of these things like LoggerToNet, LoggerToSysLog, DummyRunner, RemoveErr
and combine them

So long as I can add my OWN logics into next method of impl Iterator in new struct

But one concern I have is:
In next method:
I can only write procedural code and lose ability to call declarative adapters on inner_iter, even though they are all standard iterators

Would'n this bring some troubles and make things more complex?