Send a variable number(decided at compile time) of closures to a function such that the closures are inlined

I am writing a library which takes a variable amount of closures (the number is decided at compile time) and executes them to simulate axelrod's tournament. The closures should implement FnMut(&Vec<bool>, &Vec<bool>) -> bool
I want it to have a interface something like

let Tour=Tournament::new();
Tour.append(closure_1);
Tour.append(closure_2);
Tour.run();
dbg!(Tour.score);

I don't want to use pointer to a closure in a vector because it adds an extra indirection at runtime.
I am currently using plain function pointers instead of closures. I can't use vector of closure because they are of different sizes and types. I think compiler should be able to optimize the closure's function call. (Similar to calling a function which has been inlined).
So how should I pass closure data to the function. Here is the current code. https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b2174da35317d6c2a0602b3bd605dc19

use rand::Rng;
struct Tournament {
    players: Vec<fn(&Vec<bool>, &Vec<bool>) -> bool>,
    score: Vec<i32>,
    both_good: i32,
    both_bad: i32,
    betray: i32,
}
impl Tournament {
    fn add_player(&mut self, x: fn(&Vec<bool>, &Vec<bool>) -> bool) {
        self.players.push(x);
    }
    fn run(&mut self){
        let both_good = self.both_good;
        let both_bad = self.both_bad;
        let betray = self.betray;
        let mut rng = rand::thread_rng();
        let random_number: u32 = rng.gen_range(200..=230);
        for i in 0..self.players.len() {
            for j in 0..self.players.len() {
                let mut record_i: Vec<bool> = Vec::with_capacity(random_number as usize);
                let mut record_j: Vec<bool> = Vec::with_capacity(random_number as usize);
                for _ in 0..random_number {
                    let a = self.players[i](&record_i, &record_j);
                    let b = self.players[j](&record_i, &record_j);
                    match (a, b) {
                        (true, true) => {
                            self.score[i] = self.score[i] + both_good;
                            self.score[j] = self.score[j] + both_good;
                        }
                        (false, false) => {
                            self.score[i] = self.score[i] + both_bad;
                            self.score[j] = self.score[j] + both_bad;
                        }
                        (false, true) => {
                            self.score[i] = self.score[i] + betray;
                        }
                        (true, false) => {
                            self.score[j] = self.score[j] + betray;
                        }
                    }
                    record_i.push(a);
                    record_j.push(b);
                }
            }
        }
    }

If you want the closures to carry data and the data may be of different sizes, then they will need to be behind a pointer to give them a common size, there isn't really a way to avoid that.

If the closures are all going to be acting on a similar state, then you can store their state in its own struct, with a vector of states, and then make the functions fn(&mut State, &Vec<bool>, &Vec<bool>) -> bool (and just pass in the state when you call the function - that is really all a closure is doing, just with an anonymous state type).

If you don't actually need any state passing in, then the solution you have is sufficient.

Additionally, you should use &[u8] instead of &Vec<u8> for function arguments, since the latter doesn't give any more power and adds an extra indirection (which, ironically, is what you wanted to avoid with the closures).

1 Like

If you want full inlining potential, you need to use generics to allow monomorphization of every closure call. It's a bit more boilerplate, but can be done, e.g. as follows:

struct Player<S> {
    strategy: S,
    score: Cell<i32>,
}

trait Strategy: Fn(&[bool], &[bool]) -> bool {}
impl<F: ?Sized + Fn(&[bool], &[bool]) -> bool> Strategy for F {}

impl<S: Strategy> Player<S> {
    fn new(strategy: S) -> Self {
        Self {
            strategy,
            score: Cell::new(0),
        }
    }
}
#[derive(Debug, Copy, Clone)]
struct Scoring {
    both_good: i32,
    both_bad: i32,
    betray: i32,
}

trait Players {
    fn for_each<F: GenericCallback>(&self, callback: &mut F);
}
impl Players for NoPlayer {
    fn for_each<F: GenericCallback>(&self, _callback: &mut F) {}
}
impl<S: Strategy, P: Players> Players for OneMorePlayer<S, P> {
    fn for_each<F: GenericCallback>(&self, callback: &mut F) {
        callback.call(&self.first_player);
        self.other_players.for_each(callback);
    }
}
trait GenericCallback {
    fn call<S: Strategy>(&mut self, player: &Player<S>);
}
struct NoPlayer;
struct OneMorePlayer<S, P> {
    first_player: Player<S>,
    other_players: P,
}

macro_rules! players {
    () => {
        NoPlayer
    };
    ($strat:expr $(, $more_strats:expr)* $(,)?) => {
        OneMorePlayer {
            first_player: Player::new($strat),
            other_players: players!($($more_strats),*),
        }
    }
}

use std::cell::Cell;

use rand::Rng;
struct Tournament<P> {
    players: P,
    scoring: Scoring,
}
impl<P: Players> Tournament<P> {
    fn run(&self) {
        let random_number: u32 = rand::thread_rng().gen_range(200..=230);
        let mut record_i: Vec<bool> = Vec::with_capacity(random_number as usize);
        let mut record_j: Vec<bool> = Vec::with_capacity(random_number as usize);

        self.players.for_each(&mut OuterCallback {
            outer_self: self,
            random_number,
            record_i: &mut record_i,
            record_j: &mut record_j,
        });

        struct OuterCallback<'a, P> {
            outer_self: &'a Tournament<P>,
            random_number: u32,
            record_i: &'a mut Vec<bool>,
            record_j: &'a mut Vec<bool>,
        }

        impl<'a, P: Players> GenericCallback for OuterCallback<'a, P> {
            fn call<SI: Strategy>(&mut self, player_i: &Player<SI>) {
                self.outer_self.players.for_each(&mut InnerCallback {
                    scoring: self.outer_self.scoring,
                    random_number: self.random_number,
                    player_i,
                    record_i: self.record_i,
                    record_j: self.record_j,
                });

                struct InnerCallback<'a, SI> {
                    scoring: Scoring,
                    random_number: u32,
                    player_i: &'a Player<SI>,
                    record_i: &'a mut Vec<bool>,
                    record_j: &'a mut Vec<bool>,
                }

                impl<'a, SI: Strategy> GenericCallback for InnerCallback<'a, SI> {
                    fn call<SJ: Strategy>(&mut self, player_j: &Player<SJ>) {
                        let Self {
                            scoring,
                            random_number,
                            player_i,
                            record_i: &mut ref mut record_i,
                            record_j: &mut ref mut record_j,
                        } = *self;
                        record_i.clear();
                        record_j.clear();
                        for _ in 0..random_number {
                            let a = (player_i.strategy)(&record_i, &record_j);
                            // TODO: Is this order of arguments actually correct?
                            // or should it be (player_j.strategy)(&record_j, &record_i) ?
                            let b = (player_j.strategy)(&record_i, &record_j);
                            match (a, b) {
                                (true, true) => {
                                    player_i.score.set(player_i.score.get() + scoring.both_good);
                                    player_j.score.set(player_j.score.get() + scoring.both_good);
                                }
                                (false, false) => {
                                    player_i.score.set(player_i.score.get() + scoring.both_bad);
                                    player_j.score.set(player_j.score.get() + scoring.both_bad);
                                }
                                (false, true) => {
                                    player_i.score.set(player_i.score.get() + scoring.betray);
                                }
                                (true, false) => {
                                    player_j.score.set(player_j.score.get() + scoring.betray);
                                }
                            }
                            record_i.push(a);
                            record_j.push(b);
                        }
                    }
                }
            }
        }
    }
    fn collect_scoring(&self) -> Vec<i32> {
        struct ScoreOnlyCallback<F: FnMut(&Cell<i32>)>(F);
        impl<F: FnMut(&Cell<i32>)> GenericCallback for ScoreOnlyCallback<F> {
            fn call<S: Strategy>(&mut self, player: &Player<S>) {
                (self.0)(&player.score)
            }
        }
        let mut num_players = 0;
        self.players.for_each(&mut ScoreOnlyCallback(|_| {
            num_players += 1;
        }));
        let mut scores = Vec::with_capacity(num_players);

        self.players.for_each(&mut ScoreOnlyCallback(|score| {
            scores.push(score.get());
        }));
        scores
    }
}

fn main() {
    let tour = Tournament {
        players: players!(
            |_ri, _rj| false,
            |_ri, _rj| false,
            |_ri, _rj| false,
            |_ri, _rj| true,
            |_ri, _rj| true,
            |_ri, _rj| true,
        ),
        scoring: Scoring {
            both_good: 1,
            both_bad: 2,
            betray: 3,
        },
    };
    tour.run();
    let scores = tour.collect_scoring();
    dbg!(scores);
}
2 Likes

Thanks! Really appreciate it.

 // TODO: Is this order of arguments actually correct?
                            // or should it be (player_j.strategy)(&record_j, &record_i) ?
                            let b = (player_j.strategy)(&record_i, &record_j);

You are right. My argument order was incorrect.