Is it possible to get Fn() + PartialEq?

I know that Fn can have Copy and Clone if all captured arguments implement either of those. But is it possible to get a closure which also implements Eq and/or PartialEq?

Why I want it? Think of DSL-like syntax based around closures whene some closures will take significant time to compute. I want to avoid recomputing a closure if it or its captured arguments haven't changed.

Some requirements / limitations:

  1. no user-side workarounds. I don't want to complicate public API and make it as seamless as possible
  2. all closures are Fn, because they must not change internal state as this can lead to different result
  3. all closures are 'static so they can't capture any references. this mostly comes from the Any limitation
  4. closures might have side-effects like modifying static variables and I don't care about those right now
  5. moving closure computation to a separate thread is not an option. I want to to avoid computation itself

Here is small example of Scope from DSL:

use std::any::Any;

struct Scope {
    cached_value: usize,
    closure: Box::<dyn Any>,
}

impl Scope {
    fn new<F>(f: F) -> Self
        where F: Fn() -> usize + PartialEq + 'static
    {
        Self {
            cached_value: f(),
            closure: Box::new(f),
        }
    }

    fn lazy_compute<F>(&mut self, f: F) -> usize
        where F: Fn() -> usize + PartialEq + 'static
    {
        let Some(closure) = self.closure.downcast_mut::<F>() else {
            self.cached_value = f();
            self.closure = Box::new(f);
            return self.cached_value;
        };

        // desired functionality here
        if *closure == f {
            return self.cached_value;
        }

        self.cached_value = f();
        *closure = f;
        self.cached_value
    }
}

// 1. this is just a workaround for example to return the same closure type
// 2. I assume each returned closure from here will have the same type
fn build_closure(value: usize) -> impl Fn() -> usize {
    move || value
}

fn main() {
    let mut scope = Scope::new(|| 0);
    assert_eq!(scope.lazy_compute(build_closure(42)), 42); // closure called
    assert_eq!(scope.lazy_compute(build_closure(69)), 69); // closure called
    assert_eq!(scope.lazy_compute(build_closure(69)), 69); // closure not called
}

Problem is that... Fn doesn't implement PartialEq.
So the question is there a way to get functionality that what I want?

I don't think you can do it with closures, as types of closures are opaque.

however, if you are not limited to closures, with nightly compiler, you can implement Fn family traits on your own types. but it's you cannot use the closure syntax to auto capture variables, you'll have to construct the Fn objects manually.

Yes, they are opaque types but still can conditionally implement Copy/Clone. I hoped there is a posibility for the same treatment for PartialEq.

Custom type is not an option as it basically kills all of the DSL benefits.
Nightly doesn't help at all here because if users are forced to create their own types manually, I can just create own trait with exec method at that point.

Also see https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/PartialEq.2FEq.20for.20Closures.3F

5 Likes

thanks. now I know that I'm not the only one who needs this functionality !