Search code examples
rustclosurestrait-bounds

Enforce calling one closure before another


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?


Solution

  • 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.