0

I want to bundle two closures together in a struct. One closure is there to test if the inputs are valid, and the other closure is to actually do the stuff. Here is one variation that works:

struct Foo<T, F>
{
    test: T,
    func: F,
}

fn make_foo<A, B, T, F>(test: T, func: F) -> Foo<T, F>
where
    T: Fn(A) -> bool,
    F: Fn(A) -> B
{
    Foo {
        test,
        func,
    }
}

fn main() {
    let foo = make_foo(|a: i32| {a > 5}, |a: i32| { a + 8 });
    if (foo.test)(10) {
        println!("{}", (foo.func)(10));
    }
    else {
        println!("Input too small");
    }
}

However, Rust being Rust, there are a few issues that seem non-idiomatic. In particular, there is no check to see if test was actually run, and there is no enforcing that test and func are run with the same arguments.

So, I set out to try to fix that. My idea is that foo.test returns an Option<Fn>, where the Some variant contains func, but with the input I want to use already fed to it. So something like this as a first draft:

struct Foo<T>
{
    test: T
}

fn make_foo<A, B, T, F, H>(t: T, f: F) -> Foo<H>
where
    T: Fn(A) -> bool,
    F: Fn(A) -> B,
    H: Fn(A) -> Option<Fn() -> B>,
{
    Foo {
        test: |a: A| {
            if t(a) {
                Some(| | {f(a)})
            }
            else {
                None
            }
        }
    }
}

fn main() {
    let foo = make_foo(|a: i32| {a > 5}, |a: i32| { a + 8 });
    if let Some(f) = (foo.test)(10) {
        println!("{}", f());
    }
    else {
        println!("Input too small");
    }
}

This complains that Fn() -> B needs dyn, which of course also means it has unknown size at compile time. So we Box it:

fn make_foo<A, B, T, F, H>(t: T, f: F) -> Foo<H>
where
    T: Fn(A) -> bool,
    F: Fn(A) -> B,
    H: Fn(A) -> Option<Box<dyn Fn() -> B>>,
{
    Foo {
        test: |a: A| {
            if t(a) {
                Some(Box::new(| | {f(a)}))
            }
            else {
                None
            }
        }
    }
}

Now it complains that it expected type parameter H, found closure on the closure that I have written here, and I don't really know what that means. It also complains that it now wants an explicit type signature on foo inside main, and given that that type includes closures, I don't think that I can even do that. Also, I haven't even gotten to touch the a parameter yet. Presumably I need a move in there.

Have I dug myself into a hole here that I cannot get out of, or is there some decent way to solve this? Or is there a more ergonomic way to solve the two issues I had with my functioning code?

Arthur
  • 653
  • 2
  • 6
  • 21

1 Answers1

1

You can use an existential type instead of an universal one:

fn make_foo<A, B, T, F>(t: T, f: F) -> Foo<impl Fn(A) -> Option<Box<dyn Fn() -> B>>>
where
    A: Copy + 'static,
    T: Fn(A) -> bool,
    F: Fn(A) -> B + Copy + 'static,
{
    Foo {
        test: move |a: A| {
            if t(a) {
                Some(Box::new(move || {f(a)}) as _)
            }
            else {
                None
            }
        }
    }
}

A type parameter always means that the caller can choose a type for it, but you can't provide an arbitrary Fn(A) -> Option<Box<dyn Fn() -> B>> you only can provide a single specific value of that sort, your closure.

cafce25
  • 15,907
  • 4
  • 25
  • 31
  • You mean [something like this playground link](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=9e3526412cec5abc25ff261e73336802)? I might have made some subtle typo, but I can't get it to work. The complaint is basically the same, except it doesn't refer to `H`. – Arthur Mar 20 '23 at 12:00
  • Ah yes you have to cast the `Box`, then because of moved variables you have to add additional constraints to your parameters, I've added what the compiler suggested but your actual requirements might vary. – cafce25 Mar 20 '23 at 12:07
  • I have types with strings in them, so `Copy` is out of the question. But I managed to make it work with `Clone` instead, [here](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=e8c0c3ef1ceb14a981e0502dd2fb3dfc), basically by adding a line cloning the `a` and giving one of the clones to `t` and the other to `f` inside the boxed closure, and adding `Option B>>`. I don't know yet exactly what my actual lifetime constraints are, we will see when I implement this into my actual code. But for the moment, this looks very promising. Thank you for the help. – Arthur Mar 20 '23 at 13:04