Why is does convert a closure to a Boxed trait require an intermediate variable?

This is a bit of simplified example but it suffers from the same problem. Basically I have struct that holds a boxed Fn.

type FnParse<'a> = Box<dyn Fn() + 'a>;

struct Example<'a> {
    parse: Option<FnParse<'a>>,
}

Now if I want to construct it I tried writing the following function.

fn example1<'a, F>(f: F) -> Example<'a>
where
    F: Fn() + 'a,
{
    let parse = Some(Box::new(f));
    Example { parse }
}

Which gets the error

Example { parse }
          ^^^^^ expected trait object `dyn Fn`, found type parameter `F`

If I use a local variable then it works though.

fn example2<'a, F>(f: F) -> Example<'a>
where
    F: Fn() + 'a,
{
    let parse: Option<FnParse<'a>> = Some(Box::new(f));
    Example { parse }
}

Playground

It just works without any local variable.

fn example2<'a, F>(f: F) -> Example<'a>
where
    F: Fn() + 'a,
{
    Example { parse: Some(Box::new(f)) }
}

What's happening here? It's that Some(Box::new(f)) makes Option<Box<F>> not Option<Box<dyn Fn()>>. You can coerce Box<F> into Box<dyn Fn()> but coercing Option<Box<F>> into Option<Box<dyn Fn()>> is not supported.

In both type-specified local variable and directly assigning struct field cases, the desired type is known ahead of the evaluation of expression Some(Box::new(f)). Since it should returns Option<Box<dyn Fn()>> the Some knows it should receive Box<dyn Fn()>, it coerces Box<F> received into that type.

1 Like

By the way, the minimal change to make

fn example1<'a, F>(f: F) -> Example<'a>
where
    F: Fn() + 'a,
{
    let parse = Some(Box::new(f));
    Example { parse }
}

compile is

-   let parse = Some(Box::new(f));
+   let parse = Some(Box::new(f) as _);
1 Like