Pass Generic Function as argument

As has been mentioned, unfortunately the call is monomorphized at the call site, so you cannot pass a generic function, you can only pass a monomorphized version of the generic function.

What you can pass, however, is a function builder:

use std::fmt::Debug;

struct Builder;

impl Builder {
    fn build<I: Debug>(&self) -> fn(I) -> I {
        fn input<I: Debug>(x: I) -> I { x }
        input
    }
}

fn test<F, T: Debug>(gen: F)
    where F: Fn(Builder) -> T
{
    let builder = Builder;
    println!("{:?}", gen(builder));
}

fn main() {
    test(|builder| {
        builder.build()(10);
        builder.build()(10.0)
    });
}

The Builder is able to generate instances of input on demand.


Very interesting question! I'm pretty sure it's not possible like that.

Rust generics work by monomorphizing functions. This means that the Rust compiler will generate the machine code of the function for every concrete type the function is invoked with. Within one call of a function, the generic parameters are fixed. So since you call test exactly once in main, the generic parameters are fixed for that call.

This implies that the closure type is fixed and that the input parameter of the closure has a concrete type, too. The compiler deduces all the types for us, but if we would try to annotate these, we quickly notice that we run into the same problem as the compiler:

test::<_, usize>   // we can't ever spell out a closure type, therefore '_'
    (|input: fn(usize) -> usize|   // we can't have a generic closure right now
{
    input(10);   // works
    input(10.0)  // doesn't work
});

This looks a lot like a use case for higher kinded types and generic closures. Both of those features are not available in Rust yet, AFAIK.

However, you can still achieve what you want by using dynamic dispatch:

fn test<F, I: Debug>(gen: F) where F: Fn(fn(Box<Debug>) -> Box<Debug>) -> I {
    fn input(x: Box<Debug>) -> Box<Debug> {
        x
    }

    println!("{:?}", gen(input));
}

fn main() {
    test(|input| {
        input(Box::new(10));
        input(Box::new(10.0))
    });
}

Of course, this is not as nice as the generic version, but at least it works. Also: if you don't actually need ownership in input, you can change Box<Debug> to &Debug.

Tags:

Rust