How to use a closure as an argument of another closure?


#1

This question was also posted on stackoverflow.

Question

The code is as follows:

fn main() {
  let arg = | | println!("closure");
  let call_twice = | c | { c(); c(); };
  call_twice(arg);
}

But the compiler can’t inference the correct type of the argument c. Error message:

error: the type of this value must be known in this context

The compiler can not infer the type of the argument is an anonymous type which impls Fn.

Alternative 1

If the argument type is trait object, the code can be accepted by compiler. But the indirection isn’t necessary, since the closure is not escape.

fn main() {
  let arg = | | println!("closure");
  let call_twice = | c :&Fn() | { c(); c(); };
  call_twice(&arg);
}

Alternative 2

Using an fn can make the compiler happy, and no indirection involved. Type inference works well, but it can’t capture variables.

fn main() {
 let arg = | | println!("closure");

 // now compiler knows the argument `c` is a closure
 fn call_twice<F>(c: F) where F:Fn() {c(); c();}

 call_twice(arg);
}

My Suggestion

Can we add a syntax to support similar functionality? Such as

for<F> | c:F | where F:Fn() {c(); c();} 

Thanks!


#2

The underlying problem here is that generic functions can’t be values. Since closures are expressions, they’re used exclusively as values, so there isn’t any syntax for generic closures.

Once you’ve provided concrete types for all a generic function’s type variables, then it’s specialized and you can treat that specialization as a value. But until it’s been specialized, it really represents of a family of functions, not a specific function you can use.

That is, if I have:

fn min<T: Ord>(x: T, y: T) -> T { if x < y { x } else { y } }

then I can specialize it to a particular type and use that as a value:

let ptr_to_fn = min::<i32>;

but I can’t say:

let ptr_to_fn = min;

This restriction makes sense if we want ptr_to_fn to be represented as an actual pointer to machine code. To permit the latter, ptr_to_fn would have to be a representation of the unspecialized definition of min, and call sites would need to specialize it for their types. You’d need a JIT, or an execution model that allowed generic type parameters to be supplied at runtime (which would be slow and complex).


#3

Thanks. I got it. My suggestion doesn’t make sense.

Back to the original question, the closures are in the same scope, I suppose type inference should work it out. Is this behaviour a designed feature, or just left unimplemented behind ?


#4

generic functions can’t be values.

Correct. However,

Since closures are expressions, they’re used exclusively as values, so there isn’t any syntax for generic closures.

is incorrect.

We’re passing closures (auto generated structs implementing Fn* traits), not functions pointers. A generic call_twice closure could be desugared to:

struct CallTwice;
impl<T: FnMut()> FnOnce<(T,)> for MyClosure {
    type Output = ();
    extern "rust-call" fn call_once(self, (f,): (T,)) {
        (f)();
        (f)();
    }
}

#5

(I assume you meant CallTwice where you wrote MyClosure.)

Can you show an example of passing such a value to a (generic) function and then using it at two different types? I tried it out, and it seems to me this depends on higher-ranked types.

It seems to me that there are only two ways to handle closures as values, since they have unwritable types: you can pass them to a generic function, which has no problem being specialized for an unwritable type; or you can make a trait object of them, which erases the unwritable type.

But it looks to be like neither of these cases can preserve the generic character of the original value, the first because Rust lacks higher-ranked types; and the second because we’d have to pick a specialization of the trait impl before we could make a trait object out of it.


#6

You’re right, you can’t pass a generic closure without HKT. However, you can still define a generic closure and then use it on multiple/unspecified input types. Additionally, generic closures would allow things like:



fn test<F: Fn(&str) + Fn(u64)>(f: F) {
    // type inference issues...
    fn call_u64<F: Fn(u64)>(f: F, v: u64) {
        f(v)
    }
    fn call_str<F: Fn(&str)>(f: F, v: &str) {
        f(v)
    }

    call_str(&f, "test");
    call_u64(&f, 0u64);
}

fn main() {
   test(for<D> |v: D| where D: Debug { println!("{}", v) })
}

Regardless, on closure inspection it looks like the original problem is actually just a type inference bug. The concrete type of the closure is known by the compiler, just unnameable.


#7

Yeah, this makes sense to me. In other words, the call_twice closure has no need to be generic; it can be monomorphic, with the type of c being the unnamable closure type. I don’t see why that oughtn’t work.


#8

And my understanding is that is because Rust reifies generic functions per the caller’s specification; whereas, for example Scala could reuse the same instance of a generic function because it employs type erasure instead of reification. I explained my understanding more as follows in other thread:

Please correct me if I am mistaken.


#9

Rust can do both, generic bounds create monomorohised code with zero runtime overhead, like C++ templates. Trait-objects do dynamic dispatch with runtime dictionary passing, with the associated cost of virtual function dispatch. So Rust can do it like Scala or like C++ but with a single definition of the trait (in C++ you use templates for static polymorphism and class inheritance with virtual functions for runtime polymorphism, requiring two very different definitions if you want to use code in both ways) the programmer also has visibility of where runtime polymorphism will be used, so has to explicitly use a trait-object. This follows the principle of only paying for what you use.