There are a couple obstacles to doing this. The first is that direct use of the Fn traits themselves is unstable behind the fn_traits feature, so getting the concrete type of the argument list requires the nightly compiler. For example, the current (unstable!) representation of the arguments for Fn(u32) -> String is (u32,).
That doesn't solve the primary issue, though. The Fn family of traits has Output as an associated type, but the argument list is a generic parameter:
// vvvv
pub trait FnOnce<Args> {
type Output;
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
In other words, a type may provide different implementations of Fn depending on the types of the arguments it's called with, and each implementation may have a different output type. Here's an example with two implementations of FnOnce for a single type:
#![feature(fn_traits)]
#![feature(unboxed_closures)]
use std::ops::FnOnce;
struct Foo;
impl FnOnce<(u32,)> for Foo {
type Output = u32;
extern "rust-call" fn call_once(self, arg: (u32,)) -> u32 {
arg.0
}
}
impl FnOnce<(f32,)> for Foo {
type Output = f32;
extern "rust-call" fn call_once(self, arg: (f32,)) -> f32 {
arg.0
}
}
fn main() {
println!("Foo::call(5) = {:.1}", Foo.call_once((5_u32,)));
println!("Foo::call(5.0) = {:.1}", Foo.call_once((5.0_f32,)));
}
This outputs:
Foo::call(5) = 5
Foo::call(5.0) = 5.0
Given that an implementor of FnOnce is (from the trait's perspective) callable with multiple argument list types, and each implementation can output a different type, you must disambiguate the argument list type so the compiler can resolve which trait implementation (and thus which output type) to use.
Existing crates that abstract over functions have typically worked around the instability of the Fn traits by defining their own trait for some subset of Fn implementors, like:
trait CustomFn<Args> {
type Output;
fn custom_call(&self, args: Args) -> Self::Output;
}
// Implement CustomFn for all two-argument Fn() implementors
impl<F, A, B, Out> CustomFn<(A, B)> for F
where
F: Fn(A, B) -> Out
{
type Output = F::Output;
fn custom_call(&self, args: (A, B)) -> Self::Output {
(self)(args.0, args.1)
}
}
Generally these implementations are macro-generated to avoid having to write implementations for every number of arguments ((), (A,), (A, B), ...).
With our new custom trait, we can write this:
struct Hello<F, A>
where
F: CustomFn<A>,
{
// F::Output can be inferred from the argument types
output: F::Output,
f: F,
}
fn hello_test() {
let f = |a: u32, b: u32| a + b;
let h = Hello { f, output: 10 };
}
And the compiler can correctly infer the output type. (playground link)