0

Trying to filter a set of items, based on a predicate that is generic, because computed at run-time:

fn main () {
    let el = vec![
        vec![10, 20, 30], 
        vec![40, 50, 60]
    ];
    
    println!("{:?}", use_predicate(el, a_predicate)); // [[40, 50, 60]]
}

fn a_predicate(it: &mut impl Iterator<Item = u64>) -> bool {
    it.any(|n| n > 50)
}

pub fn use_predicate<I: Iterator<Item = u64>>(
    items: Vec<Vec<u32>>, 
    predicate: impl FnMut(&mut I) -> bool
) -> Vec<Vec<u32>> {
    items
        .into_iter()
        // ISSUE HERE
        //
        // If I collect and rewrite predicate as predicat(Vec<u64>), it works,
        // however I want to avoid allocation just to iterate over the elements.
        //
        // It also works if I just call `a_predicate` directly, but I need to 
        // pass the predicate as a parameter...
        //
        // In other words, the error doesn't happen unless I have nested generics.
        .filter(|it| predicate(&mut it.iter().map(|it| *it as u64)))
        .collect::<Vec<_>>()
}

The compilation error indicate that rustc is not able to consider the u64 iterator as a impl Iterator<Item = u64>.

error[E0308]: mismatched types
  --> src/main.rs:25:32
   |
10 | pub fn use_predicate<I: Iterator<Item = u64>>(
   |                      - this type parameter
...
25 |         .filter(|it| predicate(&mut it.iter().map(|it| *it as u64)))
   |                      --------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `I`, found struct `Map`
   |                      |
   |                      arguments to this function are incorrect
   |
   = note: expected mutable reference `&mut I`
              found mutable reference `&mut Map<std::slice::Iter<'_, u32>, [closure@src/main.rs:25:51: 25:55]>`

I am not sure how to restrict the type while avoiding to collect into a Vec<_>, which is useless cost, since we only need to iterate over the items in order to filter.

Link to the playground


EDIT: Using dyn as suggested below is not an undesirable solution, as I'm sure is still much faster than collecting into a heap allocated Vec<_>. However, in production, I am facing a lifetime issue that is difficult to solve without wrapping the predicate: FnMut in a Mutex, and unlocking each time a predicate must be evaluated. And at this point, I am not so optimistic about the performance outlook.

I made another playground to better represent the issue. Here's the issue being solved by wrapping in an Arc<Mutex<_>>. Can you think of a way to solve this without making use of the Mutex?

doplumi
  • 2,938
  • 4
  • 29
  • 45
  • 1
    The underlying issue is that the parameter that `predicate` takes is supposed to be chosen by the caller of `use_predicate`, but you've made it so that it takes a `&mut Map`, which conflicts. – BallpointBen Apr 10 '23 at 13:18
  • 1
    You don't need to allocate to be able to use dynamic dispatch, that could help in your case. Using a `&dyn Iterator` is somewhat tricky, but it can be done. Would this [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0145ec040d8d9b2b57b12a66dd3f8631) satisfy your requirements? – rodrigo Apr 10 '23 at 14:36
  • @rodrigo thanks, I didn't know that. In theory, that's a solution. In practice, it creates lifetime issue that I can't resolve without wrapping the `FnMut` closure in a Mutex. See updated [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=fecfd1a36e627f725c2ef175dbef6f7e) – doplumi Apr 11 '23 at 13:29
  • That wasn't a good repro of my issue. [Here's a better one.](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1cebc9f9ed15b1c27545e77301f55e2d) I'm thinking since the error is a type issue, it might work sending FnMut as `dyn` too. PS. This still works if we [use an Arc>](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=4561421e6979cfdca4a4f522642bba64) – doplumi Apr 11 '23 at 15:11

1 Answers1

1

Unfortunately, you can't express this type in Rust.

The first thing to understand is that generics are type arguments. That is, if a function uses a generic, the type of the generic is another input to the function, but at compile time. That's why I prefer the terminology "type parameter" rather than generic, but somehow it hasn't caught on much outside of functional programming languages.

So, the type parameter I is an input to use_predicate and gets determined at the call site. But inside the function you pass a very specific type of iterator to the predicate - one which the caller definitely did not provide. That's what expected type parameter 'I', found struct 'Map' is saying.

Perhaps you also tried:

pub fn use_predicate<P>(
    items: Vec<Vec<u32>>, 
    predicate: impl FnMut(&mut impl Iterator<Item = u64>) -> bool
) -> Vec<Vec<u32>> { ... }

This isn't allowed. The only way that this could currently be desugared in Rust is to essentially the same thing as you gave in your question. It would have the same problem, but also probably isn't the way that the Rust developers would want to desugar that. More likely, they'd want to desugar it using higher-ranked bounds:

pub fn use_predicate<P>(items: Vec<Vec<u32>>, predicate: P) -> Vec<Vec<u32>>
where
    P: for<I: Iterator<Item = u64>> FnMut(&mut I) -> bool { ... }

Now this is exactly what you want! Sadly, this isn't currently supported, and I haven't seen very much enthusiasm for such a feature or any movement towards implementing it.


So what can you do?

You may be able to simplify your requirement. But, assuming you can't, you can use dynamic dispatch:

fn a_predicate(mut items: &mut dyn Iterator<Item = u64>) -> bool {
    // Clunky workaround because Iterator::any isn't object-safe
    (&mut items).any(|n| n > 50)
}

pub fn use_predicate(
    items: Vec<Vec<u32>>,
    mut predicate: impl FnMut(&mut dyn Iterator<Item = u64>) -> bool,
) -> Vec<Vec<u32>> {
    items
        .into_iter()
        .filter(|items| predicate(&mut items.iter().map(|&n| n as u64)))
        .collect::<Vec<_>>()
}
Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • `.any()` does work if you `(&mut it)` the iterator first. See @rodrigo comment below OP. Is that undesirable? Also, while this is indeed a solution to the playground posted above, it doesn't work in a closure without locking/unlocking each time we need to use the `predicate`, which I am not sure is worse performance-wise than heap allocation. See [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=fecfd1a36e627f725c2ef175dbef6f7e) Do you think that could be mitigated without a `Mutex`? – doplumi Apr 11 '23 at 13:30
  • That wasn't a good repro of my issue. [Here's a better one.](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1cebc9f9ed15b1c27545e77301f55e2d) I'm thinking since the error is a type issue, it might work sending FnMut as `dyn` too. PS. This still works if we [use an Arc>](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=4561421e6979cfdca4a4f522642bba64) – doplumi Apr 11 '23 at 15:11
  • @doplumi Thanks. I've updated with that. – Peter Hall Apr 11 '23 at 17:24