Can we generically wrap a struct with closure field?

I want to reuse a container implementation but tweak the interface, like BTreeSet is an adaptation of BTreeMap. Take this silly vector pretending to be storing pairs (playground).

#![feature(drain_filter)]

struct PairedWith42<T>(Vec<T>);

struct DrainFilter<'a, T>(std::vec::DrainFilter<'a, T, Box<dyn 'a + FnMut(&mut T) -> bool>>);

impl<T> Iterator for DrainFilter<'_, T> {
    type Item = (T, i8);

    fn next(&mut self) -> Option<(T, i8)> {
        self.0.next().map(|e| (e, 42))
    }
}

impl<T> PairedWith42<T> {
    fn drain_filter<'a, F>(&'a mut self, mut pred: F) -> DrainFilter<'a, T>
    where
        F: 'a + FnMut(&T, i8) -> bool,
    {
        DrainFilter(self.0.drain_filter(Box::new(move |e| pred(e, 42))))
    }
}

fn main() {
    let mut v = PairedWith42(vec![1, 2, 3]);
    assert_eq!(v.drain_filter(|e, _| *e > 1).count(), 2);
    assert_eq!(v.0.len(), 1);
}

I left out most methods because they're "easy", except drain_filter, the only method that returns a closure stored in a struct. Since there's no way to write the type of any closure, and you need to specify the return value type in the adapted interface, I guess there's no way around boxing the closure, even though it has only one implementation. Or is there?

Another way is fn drain_filter<'a, F>(...) -> impl Iterator<Item = T> + 'a. This puts "can't name the type" problem onto the user's shoulders. If they need to name it, they'd have to box it, but for something like drain_filter you usually don't have to name it. Well, unless you are writing a wrapper over a DrainFilter :wink:

1 Like

OK, so more accurately with Item = (T, i8):

#![feature(drain_filter)]

struct PairedWith42<T>(Vec<T>);

impl<T> PairedWith42<T> {
    fn drain_filter<'a, F>(&'a mut self, mut pred: F) -> impl Iterator<Item = (T, i8)> + 'a
    where
        F: 'a + FnMut(&T, i8) -> bool,
    {
        self.0.drain_filter(move |e| pred(e, 42)).map(|e| (e, 42))
    }
}

Still somewhat disappointing though.

Turns out that a better way would be for the bulk of the std::vec::DrainFilter data and methods to in a separate struct, e.g. DrainFilterImpl, without containing the predicate, but passing the predicate as argument to DrainFilterImpl::next(). std::vec::DrainFilter or whatever similar actual iterator must be written out reusing that struct and simply delegating to the methods.

Similar code for BTreeMap and BTreeSet sharing a DrainFilter implementation is currently a pull request

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.