How to deal with closures taking closures as parameters

Hey, I am currently trying to achieve the following:
I have a Self which is just a Vec and it represents a square of pixels flattened to 1D. So to get pixel(x,y) I would return &self[x + width*y]. Now I want to get a specific subset of my square which is one of the 4 v-shaped parts enclosed by the two diagonals:
quadrants

for this purpose I wrote the following method (because I wanted to have as few redundant code as possible):

fn get_quadrant(&self, quadrant: Quadrant) -> Vec<&T> {
        let width = self.side_length();
        let get_triangle =  <some closure>;
        let get_quadrant_like = <another closure>

        match quadrant {
            Quadrant::RightTriangularQuadrant => {
                let columns = get_triangle(0, width);
                let f: fn(usize) -> usize = |x: usize| width - 1 - x;
                let g = |a: usize, b: usize| (a,b);
                get_quadrant_like(f,g)
            },
            Quadrant::TopTriangularQuadrant => todo!(),
            Quadrant::LeftTriangularQuadrant => todo!(),
            Quadrant::BottomTriangularQuadrant => todo!(),
            _ => todo!(),
        }
    }

Here are the two helper closures I use in more detail, first get_triangle:

        let get_triangle = |mut start: usize, mut end: usize| {
            let mut result = Vec::with_capacity(width/2);
            while end - start > 0 {
                let mut row = Vec::with_capacity(width/2);
                for i in start..end {
                    row.push(i);
                }
                result.push(row);
                start -= 1;
                end -= 1;
            }
            result
        };

and second get_quadrant_like

        let get_quadrant_like = 
        |f: fn(usize) -> usize, 
         g: fn(usize, usize) -> (usize, usize)| {
            let columns = get_triangle(start, width);
            let mut result = Vec::with_capacity(self.len()/2);
            for (i, column) in columns.iter().enumerate() {
                let fixed = f(i);
                for loose in column.iter() {
                    let (x, y) = g(fixed, *loose);
                    result.push(self.get(x, y));
                }
            }
            result
        };

The compiler complains in my match expression when I try to define the function f:

mismatched types
expected fn pointer fn(usize) -> usize
found closure {closure@src\evolution\gene.rs:119:45: 119:55}
and
closures can only be coerced to fn types if they do not capture any variables

I also tried to redefine the fn() -> ... type to impl Fn() but this just created problems somewhere else. I am new to rust and have not used closures that often. Does someone maybe know where the problem might be?
Thank you very much!

Your closure captures width. You may be able to get rid of the requirements of a function pointer having no state by removing all type annotations of fn(...), or maybe replacing them with impl Fn(...).

Closures can't be generic over types. You don't technically need that here, since you only pass in f and g.[1] But given the structure of your code, the compiler wants to know the types of the inputs to get_quadrant_like ahead of time.

But you can't name the types of closures. Since you can't name the closures and can't use generics, you need to either

  • Rearrange your code so that the compiler can infer the types[2]
  • Type erase the closure types into some nameable type

Coercing to a function pointer is one way to erase the closure type, but that only works for closures that don't capture anything.

Another way to type erase things is to use dyn Trait. For your example, that would look like so:

        let get_quadrant_like = |
            f: &dyn Fn(usize) -> usize, // fn(usize) -> usize, 
            g: &dyn Fn(usize, usize) -> (usize, usize), // fn(usize, usize) -> (usize, usize)
        | {
            ..
        }

        // ...

                let f = |x: usize| width - 1 - x;
                let g = |a: usize, b: usize| (a,b);
                get_quadrant_like(&f, &g)

Depending on the closure, you might need &mut dyn FnMut(..) instead.

If you needed to pass ownership of the closure elsewhere for some reason, you would need Box<dyn FnMut(..)> or similar instead (but your example doesn't look like that use case to me).

Another possibility is making the closure not capture by adding an input parameter for width, say (and keep using fn(..)).

Argument-position impl Trait (APIT) is a form of being generic over types, and thus isn't supported in closure arguments.


  1. see next post ↩︎

  2. not covered in this reply not applicable ↩︎

4 Likes

Nevermind that part, you do need that here, it was just hidden behind todos.

Another alternative to closures would be macros.

1 Like

on the subject of macros, usually rust's macro hygiene system would prevent a macro from accessing local variables, but if you write a macro_rules! macro within the function body, it can access that function's local variables (or at least the ones declared before the macro)

1 Like

You can make get_triangle and get_quadrant_like to be functions, as rust allows local functions.
Then, functions can take lambda as a generic parameter with respective Fn trait.
Then, they will have to take extra width parameter, because they cannot capture it anymore.

It does not directly answer your question, and it probably does not have an answer. Either a closure takes a function or a function takes a closure.

Thank you everyone for the fast help! I just used &dyn Fn() as type and it compiled :+1:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.