Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I write a convenient API that accepts a list of functions?

Tags:

rust

Let's say I wanted to write a concurrency function that takes N functions as input, calls them concurrently, and waits for all of them to finish. Like std::thread::spawn, but for when there are a fixed number of functions and I don't need to spawn them incrementally. In theory I think I can accept them like this:

fn fan_out(_fns: &[&dyn Fn()]) {
    todo!();
}

However, in practice it seems it's not possible to call this conveniently unless all of the functions have the same type underneath. For example, this doesn't work because each input is not already a &dyn Fn():

fan_out(&[
    || println!("taco"),
    || println!("burrito"),
]);

…and then also probably later I'd get a compiler error about how they have different types because of course you can't have an array of disparate types.

Is there any way to structure this so I can have approximately that syntax when calling fan_out? In C++ for example I'd be able to do the following, since the lambdas implicitly convert to std::function<void()>:

void FanOut(std::vector<std::function<void()>> fns);

FanOut({
    [] { std::cout << "taco\n"; },
    [] { std::cout << "burrito\n"; },
});
like image 505
jacobsa Avatar asked Oct 15 '25 03:10

jacobsa


2 Answers

This code runs just fine:

fn my_fun(fns: &[&dyn Fn()]) {
    for f in fns {
        f();
    }
}

fn main() {
    let a = || println!("A");
    let b = || println!("B");

    my_fun(&[&a, &b]);
}

This works because a and b are values with different, unique closure types, and &[&a, &b] coerces &a and &b to &dyn Fn().

You could do that explicitly as well:

    let arr: [&dyn Fn(); 2] = [&(|| println!("A")), &(|| println!("B"))];

    my_fun(&arr);

But when you try to do it directly, you're passing two values with different and unique closure types:

    my_fun(&[|| println!("A"), || println!("B")]);

That can't work.

You could also box the closures, if you need a bit more control over the closures in your function:

fn my_fun(fns: &[Box<dyn Fn()>])
{
    for f in fns {
        f();
    }
}

fn main() {
    my_fun(&[ 
        Box::new(|| println!("A")), 
        Box::new(|| println!("B")), 
    ]);
}
like image 136
Grismar Avatar answered Oct 19 '25 09:10

Grismar


This doesn't work because a closure doesn't automatically coerce to &dyn Fn(). You have to take a reference to it. This isn't even unique to closures, this is common to all &dyn _ types -- impl Foo never automatically coerces to &dyn Foo, but &impl Foo can.

That is, while this does not compile:

fan_out(&[|| println!("taco"), || println!("burrito")]);

This does:

fan_out(&[&|| println!("taco"), &|| println!("burrito")]);

To me this seems to qualify as "approximately that syntax."

like image 25
cdhowie Avatar answered Oct 19 '25 09:10

cdhowie