首页 文章

将通用函数作为参数传递

提问于
浏览
7

我希望能够将泛型函数传递给另一个函数(在本例中为闭包),而不会丢失传递函数的“泛型” . 由于这是一个非常复杂的陈述,这是一个例子:

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)
                        ^~~~

生锈有可能吗?

编辑:

基于给出的解决方案,我使用以下方法来解决类似的问题:

#![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)
    });
}

2 回答

  • 7

    如前所述,不幸的是,调用在调用站点被单态化,因此您无法传递泛型函数,您只能传递泛型函数的单态化版本 .

    但是,您可以传递的是函数构建器:

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

    Builder 能够根据需要生成 input 的实例 .

  • 4

    非常有趣的问题!我不可能那样 .

    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还没有,AFAIK .

    但是,您仍然可以使用动态分派实现所需的功能:

    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 .

相关问题