Search code examples
rustclosures

Why does this closure not implement Fn?


I'm trying to implement a function which will construct an iterator over the whole range of possible indices for several related 2D arrays (which always have the same size). However, the compiler is giving me an error, saying that the closure I'm passing to map does not implement Fn, only FnMut. This is my original code:

pub fn get_index_iterator(&self) -> Map<Range<usize>, impl Fn(usize) -> (usize, usize)> {
        (0usize..(self.width * self.height))
            .map(|idx| -> (usize, usize) {
                (idx % self.width, idx / self.width)
            }
        )
    }

In an attempt to figure out what was going on, I eventually tried replacing that with this code:

pub fn get_index_iterator(&self) -> Map<Range<usize>, impl Fn(usize) -> (usize, usize)> {
        (0usize..(self.width * self.height))
            .map(|idx| -> (usize, usize) {
                (0usize, 0usize)
            }
        )
    }

Just to see if the error would change to something more helpful, but it did not, the error remains

expected a `std::ops::Fn<(usize,)>` closure, found `[closure@src\lib.rs:33:18: 33:41]`
the trait `std::ops::Fn<(usize,)>` is not implemented for closure `[closure@src\lib.rs:33:18: 33:41]`
`[closure@src\lib.rs:33:18: 33:41]` implements `FnMut`, but it must implement `Fn`, which is more general

for both versions. I cannot see how a closure returning a constant and doing nothing else could possibly mutate the state of it's environment. What am I missing here?


Solution

  • I don't know why this happens, but if you make the closure its own variable, it works in both cases.

    pub fn get_index_iterator(&self) -> Map<Range<usize>, impl Fn(usize) -> (usize, usize) + '_> {
        let f = |idx| -> (usize, usize) { (idx % self.width, idx / self.width) };
        (0usize..(self.width * self.height)).map(f)
    }
    

    You need + '_ since it captures self, but it's still a Fn as it should be. So you're correct that this closure should work as Fn. It might be that map is downgrading the closure somehow, but then I don't know how passing it as a variable works.

    Besides this, you could write this as impl Iterator to avoid this entirely. This is preferred since if you want to optimize this later, you can change the iterator type without breaking your API. It is also shorter. You need mut to call next on Map anyway, so making the closure Fn instead of FnMut doesn't provide any benefit.

    pub fn get_index_iterator(&self) -> impl Iterator<Item = (usize, usize)> + '_
    

    Here's a shorter version that shows the same behavior.

    fn map<F: FnMut()>(f: F) -> F {
        f
    }
    
    pub fn returns_map() -> impl Fn() {
        // let c = || {};
        // map(c)
        map(|| {})
    }