EN VI

Pointers - Why is calling Box-ed closure requires unstable fn_traits?

2024-03-10 16:30:04
Pointers - Why is calling Box-ed closure requires unstable fn_traits?

Consider the following code:

#![feature(fn_traits)]
fn test_fn_2_args<F>(f: Box<F>)
where
    F: Fn(i32, i64) -> i32
{
    <Box<F> as Fn<(i32, i64)>>::call(&f, (1, 2));
}

fn main(){
    test_fn_2_args(Box::new(|first, second| {
       println!("{first}, {second}");
       first
    }));
}

Playground

This code requires unstable fn_traits, but this is not clear why. As per fn_traits docs it's designed for implementing closure-like type which I don't do in this example.

All I do is interpret Box<Fn> as Fn which is not clear why is allowed as well since Box<Fn> does not implement Fn obviously.

Solution:

Calling a boxed closure, does not require unstable. To call it, you simply use the standard calling syntax (i.e. a call expression) to call it. Boxed or not.

It doesn't matter whether you have Fn() or Box<dyn Fn()> you call it in the same way, i.e. f().

The reason you can call them in the same way, is because there is a blanket implementation of impl<F> Fn for Box<F> where F: Fn.

The reason you can use Fn, but not Fn::call() is because Fn is stable, while Fn::call() is unstable. Which as you've discovered is part of the unstable fn_traits feature (see issue #29625).

So the issue is not "interpreting Box<Fn> as Fn", the issue is you're attempting to call the unstable Fn::call().

In short, doing this is stable:

fn f<F: Fn()>(f: F) {
    f();
}

fn boxed_f<F: Fn()>(f: Box<F>) {
    f();
}

While doing the following is unstable:

fn f<F: Fn()>(f: F) {
    Fn::<()>::call(&f, ());
}

fn boxed_f<F: Fn()>(f: Box<F>) {
    Fn::<()>::call(&f, ());
}

All in all, just call the closure like any other function:

fn test_fn_2_args<F>(f: Box<F>)
where
    F: Fn(i32, i64) -> i32,
{
    f(1, 2);
}

You don't even need Box:

fn test_fn_2_args<F>(f: F)
where
    F: Fn(i32, i64) -> i32,
{
    f(1, 2);
}

Again, since there is a blanket implementation of impl<F> Fn for Box<F> where F: Fn. That also means that changing test_fn_2_args() to not use Box, still allows you to call it using a boxed closure. So the following compiles perfectly fine.

fn test_fn_2_args<F>(f: F)
where
    F: Fn(i32, i64) -> i32,
{
    f(1, 2);
}

fn main() {
    test_fn_2_args(|first, second| {
        println!("{first}, {second}");
        first
    });

    test_fn_2_args(Box::new(|first, second| {
        println!("{first}, {second}");
        first
    }));
}
Answer

Login


Forgot Your Password?

Create Account


Lost your password? Please enter your email address. You will receive a link to create a new password.

Reset Password

Back to login