1

I want to call a custom defined filter in multiple struct methods, but it should somehow have access to a certain property of the struct.

Here is a basic example of what I have so far:

struct Collection {
    elements: Vec<isize>,
    scope: isize
}

impl Collection {
    fn action1(&self) {
        for e in (&self.elements).iter().filter(Collection::filter) {
            println!("do something ...");
        }
    }

    fn action2(&self) {
        // filter and do something with the elements
    }

    fn action3(&self) {
        // filter and do something with the elements
    }

    fn filter(cur: &&isize) -> bool {
        // Determine if current element needs to be filtered based on the scope value
        true
    }
}

fn main() {
    let c = Collection { elements: vec![1, 2, 3, 4, 5, 6, 7, 8], scope: 2 };
    c.action1();
    c.action2();
}

I'm aware that I could pass a closure / block directly as an argument, but that would imply copying the filter logic across multiple methods, which is what I want to avoid.

It would had been nice to be able to do the following:

fn filter(&self, cur: &&isize) -> bool {

}

However this won't compile, most likely because it's really a method, not a function.

Perhaps this is doable if the function returns a closure, but I just couldn't make the filter accept it as a response.

Having this said, how should I handle the data filtering?

Community
  • 1
  • 1
vise
  • 12,713
  • 11
  • 52
  • 64

1 Answers1

2

Things that don't work:

  • You can't define a function or method that returns a closure because closure types are anonymous and can't be named. I tried boxing the result, but it doesn't look like the Fn* traits work through indirection (might not have been implemented yet).

  • You can almost define a filter_by_scope method that takes the iterator itself, filters it, and returns the result... except that you can't name the return type because it would contain a closure type.

  • You could define a ScopeFilter type that implements the FnMut(&&isize) -> bool interface... except because the traits are unstable, that's not really a good idea (even if it works now, it definitely won't work with 1.0).

  • You can't pass a member function/method to filter because it won't partially apply the self argument.

The simplest thing I can think of is to just call the filter logic method directly:

struct Collection {
    elements: Vec<isize>,
    scope: isize
}

impl Collection {
    fn action1(&self) {
        for e in self.elements.iter().filter(|e| self.filter(e)) {
            println!("got {:?}", e);
        }
    }

    fn filter(&self, cur: &&isize) -> bool {
        // Determine if current element needs to be filtered based on the scope value
        **cur <= self.scope
    }
}

fn main() {
    let c = Collection { elements: vec![1, 2, 3, 4, 5, 6, 7, 8], scope: 2 };
    c.action1();
}

Yes, you have to write the closure everywhere you use it... but it's only six extra characters, and the only other thing I can think of would be a macro, which would be even longer.

So yeah. This is, I think, something that near future Rust will handle quite reasonably, less so right now.

DK.
  • 55,277
  • 5
  • 189
  • 162
  • It adds a bit of code duplication but not overly so. I like this approach, thank you! – vise Jan 24 '15 at 12:43
  • [`Fn*` traits do work through indirection](http://is.gd/ieAsSW). That said, your answer results in more efficient code than having to allocate a box for the closure, so this is still the best approach. – wingedsubmariner Jan 25 '15 at 06:25
  • @wingedsubmariner: That's not a relevant example. The `filter` method expects a type that implements `FnMut(..)`. Although you can call a `Fn*` through a box (via auto-`Deref`), a `Box` does not itself implement the `Fn*` trait, which is what I was referring to. You can't deref the box to get at the closure either, since you'd get a trait type and those are unsized. – DK. Jan 25 '15 at 09:45