将通用函数作为参数传递

25

我想要能够将一个泛型函数传递给另一个函数(在这种情况下是闭包),而不丢失传递函数的“泛型性”。因为这是一个相当复杂的陈述,所以这里有一个例子:

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)
    });
}

这段代码无法编译通过,因为 input 的值被推断出来后不再是泛型。

完整错误信息:

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

这在Rust中是否可能?编辑:基于给出的解决方案,我已经使用以下方法来解决类似的问题:
#![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)
    });
}

1
我不确定这会起作用吗?你正在调用test,它将根据对gen闭包的第一次调用的推断而单态化为u32。如果你想要一个单独的..你需要再次调用test..分别。我在脑海中“编译”这个程序,但我并没有看到它在这种或任何其他语言中是如何可能的,因为推断将会逐级下降。也许我错了。 - Simon Whitehead
2个回答

17

正如之前提到的,很遗憾调用在调用站点被单态化,因此您不能传递一个通用函数,只能传递一个单态化的通用函数版本。

然而,您可以传递一个函数构建器:

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)
    });
}
< p > Builder 能够根据需要生成 input 的实例。 < /p >

9
非常有趣的问题!我非常确定它不可能像那样实现。
Rust泛型通过单态化函数来工作。这意味着Rust编译器将为每个具体类型调用该函数生成机器代码。在一次函数调用中,泛型参数是固定的。因此,由于您在main中只调用了test一次,因此泛型参数对于该调用是固定的。
这意味着闭包类型是固定的,并且闭包的input参数也具有具体类型。编译器为我们推断出所有类型,但是如果我们尝试注释这些类型,我们很快就会发现我们遇到了与编译器相同的问题:
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
});

这看起来很像是需要使用高阶类型和通用闭包的案例。据我所知,这两个特性在 Rust 中还不可用。

然而,您仍然可以通过使用动态分发来实现您想要的功能:

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))
    });
}

当然,这不如通用版本好,但至少它有效。另外:如果您实际上不需要在input中使用所有权,您可以将Box<Debug>更改为&Debug

这是一个很好的答案。我选择了另一个答案,因为我的解决方案与它更相似,而不是你的答案,但是你对问题的解释非常好。谢谢! - dpzmick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接