【问题标题】:How can I pass a closure with generics to a function without making that function generic?如何在不使该函数泛型的情况下将带有泛型的闭包传递给函数?
【发布时间】:2019-03-03 12:47:20
【问题描述】:

我有一个与枚举一起使用二进制函数的函数。这是给口译员的:

use std::ops::*;

#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum Scalar {
    I64(i64),
    I32(i32),
    //many many others
}

pub trait TMath: Add + Mul + Sized {} //mark numerical types
impl<T: Add + Mul> TMath for T {}

fn add<T: TMath>(x: T, y: T) -> <T as Add>::Output {
    x + y
}

pub type NatBinExpr<T: TMath> = Fn(&T, &T) -> T;

我想做:

let result = bin_op(add, &Scalar::I32(1), &Scalar::I32(2));

还要让它适用于任意二进制函数:

let result = bin_op(Scalar::concat, &Scalar::I32(1), &Scalar::I32(2));

但是,我还没有找到一种方法来通过闭包而不使bin_op 泛型:

fn bin_op(apply: &NatBinExpr???, x: &Scalar, y: &Scalar) -> Scalar {
    match (x, y) {
        (Scalar::I64(a), Scalar::I64(b)) => Scalar::I64(apply(a, b)),
        (Scalar::I32(a), Scalar::I32(b)) => Scalar::I32(apply(a, b)),
    }
}

使bin_op 泛型是不对的; bin_opScalar 进行操作,但内部操作是通用的。

我原来asked this question on Reddit

【问题讨论】:

  • 看起来你需要更高种类的类型来表达这一点,目前 rust 不支持。

标签: generics rust closures


【解决方案1】:

基本上有两种不同的方式来谈论函数类型:

  • 指针:fn(A, B) -&gt; C,
  • 特征:Fn(A, B) -&gt; CFnMut(A, B) -&gt; CFnOnce(A, B) -&gt; C

在任何一种情况下,它们都以参数和结果类型为特征。

那么,apply 的参数和结果类型是什么?

视情况而定。

从您的示例中,我们可以看到[i64, i32, ...] 中的TFnOnce(T, T) -&gt; T

这不是 one 类型,这是 许多 类型。因此,它需要的不是单一功能,而是多种功能;或者可能是一个函数对象多次实现FnOnce


函数对象路由仅在夜间可用,并且需要大量样板文件(宏会有所帮助):

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

use std::ops::*;

#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum Scalar {
    I64(i64),
    I32(i32),
    //many many others
}

pub trait TMath: Add + Mul + Sized {} //mark numerical types

impl<T: Add + Mul> TMath for T {}

struct Adder;

impl FnOnce<(i64, i64)> for Adder {
    type Output = i64;
    extern "rust-call" fn call_once(self, args: (i64, i64)) -> i64 {
        args.0 + args.1
    }
}

impl FnMut<(i64, i64)> for Adder {
    extern "rust-call" fn call_mut(&mut self, args: (i64, i64)) -> i64 {
        args.0 + args.1
    }
}

impl Fn<(i64, i64)> for Adder {
    extern "rust-call" fn call(&self, args: (i64, i64)) -> i64 {
        args.0 + args.1
    }
}

impl FnOnce<(i32, i32)> for Adder {
    type Output = i32;
    extern "rust-call" fn call_once(self, args: (i32, i32)) -> i32 {
        args.0 + args.1
    }
}

impl FnMut<(i32, i32)> for Adder {
    extern "rust-call" fn call_mut(&mut self, args: (i32, i32)) -> i32 {
        args.0 + args.1
    }
}

impl Fn<(i32, i32)> for Adder {
    extern "rust-call" fn call(&self, args: (i32, i32)) -> i32  {
        args.0 + args.1
    }
}

fn bin_op<F>(apply: &F, x: Scalar, y: Scalar) -> Scalar
    where
        F: Fn(i64, i64) -> i64,
        F: Fn(i32, i32) -> i32,
{
    match (x, y) {
        (Scalar::I64(a), Scalar::I64(b))
            => Scalar::I64((apply as &Fn(i64, i64) -> i64)(a, b)),
        (Scalar::I32(a), Scalar::I32(b))
            => Scalar::I32((apply as &Fn(i32, i32) -> i32)(a, b)),
        _ => unreachable!(),
    }
}

fn main() {
    let result = bin_op(&Adder, Scalar::I32(1), Scalar::I32(2));
    println!("{:?}", result);
}

Prints I32(3).

【讨论】:

  • 这和正常的方式一样冗长还是更多?如果它不能折叠以仅通过 (A, B) -> C 而没有实现每个运算符,那么我看不到重点。
  • @mamcx:这绝对是冗长的事情,基本上归结为 Rust 不支持重载。老实说,此时我会使用自定义trait BinOp { fn on_i64(&amp;self, i64, i64) -&gt; i64; fn on_i32(&amp;self, i32, i32) -&gt; i32; ... }(每种类型都有一个方法),然后为AdderMuler等实现它...
  • @mamcx 你可以创建一个macro_rules! 宏来减少重复。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多