Pass a closure to a function expecting a function?


#1

I’m trying to create a function that accepts two factory functions as arguments. It looks like this:

type StratFactory = fn( player : usize ) -> Box<Strategy>;

fn score_one( p0 : StratFactory, p1 : StratFactory ) -> i32 {

I wish to however initialize this with a closure, such as below:

let p0 = |player| Box::new(StratMonteCarloPredict::new(player, 30, Some((0,0)), 10 ));
let p1 = |player| Box::new(StratMonteCarloPredict::new(player, 30, None, 10 ));

let score : i32 = [0;ITER].par_iter().map(|_| score_one(p0,p1)).sum();

Where StratMonteCarloPredict implements the Strategy Trait. I’m getting errors on the call to score_one about invalid types expected trait tic_tac_toe::strategy::Strategy, found structtic_tac_toe::strategy::StratMonteCarloPredict`

I’ve discovered that closures do not work as functions, strange but okay. I tried an approach with making score_one a template, but kept running into more problems.

How do I make a functcion accept either a function or closure as an argument?


#2

One option here is to use generics:

fn score_one<T:Strategy,StratFactory1,StratFactory2>( p0 : StratFactory1, p1 : StratFactory2 ) -> i32
	where StratFactory1:Fn( usize ) -> Box<T>, StratFactory2:Fn( usize ) -> Box<T>

With T:Strategy to solve the mismatch of types you are seeing. And StratFactory1 and StratFactory2 because each closure has a different type, even if they have the same parameters.


#3

Functions by definition can’t have any local context. They are just a single pointer with no ability to pass any additional data other than function’s arguments.

Closures do capture their context, and therefore aren’t just function pointers, but objects with arbitrary data.

Because each closure could capture different variables from different context, each closure is a different type, so you need generics to make your function accept any closure-ish type.

fn score_one<F0, F1>( p0: F0, p1: F1 ) where F0: FnOnce(usize) -> Box<Strategy>, F1: FnOnce(usize) -> Box<Strategy>

#4

To add to what others have said, your original code would be fine if you coerced the closure to the StratFactory (i.e. fn pointer) type:

let p0: StratFactory = |player| Box::new(StratMonteCarloPredict::new(player, 30, Some((0,0)), 10 ));
let p1: StratFactory = |player| Box::new(StratMonteCarloPredict::new(player, 30, None, 10 ));

A non-capturing closure can be coerced to an fn pointer but an fn pointer cannot be coerced or cast to a closure. So, you’re better off using generics with a closure parameter, and if you need/want, you can pass appropriate fn pointers instead.