Pass Generic Function as argument
Asked Answered
V

2

25

I would like to be able to pass a generic function to another function (in this case a closure), without losing the "genericness" of the passed function. Since that's a pretty convoluted statement, here's an example:

use std::fmt::Debug;

fn test<F, I: Debug>(gen: F) where F: Fn(fn(I) -> I) -> I {
    fn input<I: Debug>(x: I) -> I {
        x
    }
    
    println!("{:?}", gen(input));
}

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

This will not compile, because the value of input is type inferenced and no longer generic.

Full error:

<anon>:14:15: 14:19 error: mismatched types:
 expected `_`,
    found `_`
(expected integral variable,
    found floating-point variable) [E0308]
<anon>:14         input(10.0)
                        ^~~~

Is such a thing possible in rust?

edit:

Based on the solutions given, I've used the following to solve a similar problem:

#![feature(unboxed_closures)]
#![feature(fn_traits)]

use std::ops::Fn;
use std::ops::Add;
use std::ops::FnMut;

use std::fmt::Debug;

struct Builder;

impl Builder {
    pub fn build<A: Add<B>, B: Add<A>>(&self) -> fn(A, B) -> <A as std::ops::Add<B>>::Output {
        fn c<A: Add<B>, B: Add<A>>(a: A, b: B) -> <A as std::ops::Add<B>>::Output {
            a + b
        }
        
        return c;
    }
}

impl<A: Add<B>, B: Add<A>> Fn<(A, B)> for Builder {
    extern "rust-call" fn call(&self, args: (A, B)) -> <A as std::ops::Add<B>>::Output {
        let (a1, a2) = args;
        self.build()(a1, a2)
    }
}

impl<A: Add<B>, B: Add<A>> FnMut<(A, B)> for Builder {
    extern "rust-call" fn call_mut(&mut self, args: (A, B)) -> <A as std::ops::Add<B>>::Output {
        let (a1, a2) = args;
        self.build()(a1, a2)
    }
}

impl<A: Add<B>, B: Add<A>> FnOnce<(A, B)> for Builder {
    type Output = <A as std::ops::Add<B>>::Output;
    extern "rust-call" fn call_once(self, args: (A, B)) -> <A as std::ops::Add<B>>::Output {
        let (a1, a2) = args;
        self.build()(a1, a2)
    }
}

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

fn main() {
    test(|builder| {
        builder(10, 10);
        builder(10.1, 10.0)
    });
}
Violetvioleta answered 3/6, 2016 at 4:37 Comment(2)
rust playground link: play.rust-lang.org/…Violetvioleta
I'm... not sure this would ever work? You're calling test which will be monomorphized to be u32 based on the inference of the first call to the gen closure. If you want a separate one.. you'll need to call test again.. separately. I'm "compiling" this in my head and I just don't see how it would be possible in this or any other language, because the inference will waterfall down. Perhaps I am wrong though.Haddad
D
18

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.

Divertissement answered 3/6, 2016 at 7:15 Comment(0)
A
10

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.

Allamerican answered 3/6, 2016 at 6:42 Comment(1)
This is a great answer. I've accepted the other answer since my solution is more similar to it than it is to your answer, but you explanation of the issue is very, very good. Thank you!Violetvioleta

© 2022 - 2024 — McMap. All rights reserved.