如何为不同类型的参数实现一个结构的 Fn 特性?

How do I implement the Fn trait for one struct for different types of arguments?

我有一个简单的分类器:

struct Clf {
    x: f64,
}

分类器 returns 如果观察值小于 x 则为 0,如果观察值大于 x 则为 1。

我想为这个分类器实现调用运算符。但是,该函数应该能够将浮点数或向量作为参数。在向量的情况下,输出是 0 或 1 的向量,其大小与输入向量相同:

let c = Clf { x: 0 };
let v = vec![-1, 0.5, 1];
println!("{}", c(0.5));   // prints 1
println!("{}", c(v));     // prints [0, 1, 1]

在这种情况下如何编写 Fn 的实现?

impl Fn for Clf {
    extern "rust-call" fn call(/*...*/) {
        // ...
    }
}

你不能(但请阅读直到答案结束)。

首先,显式实现 Fn* 特征系列是不稳定的,并且随时可能发生变化,因此依赖于此是个坏主意。

其次,更重要的是,Rust 1.33 nightly 之前的 Rust 编译器 不会 让您调用具有 Fn* 不同参数类型实现的值.它只是无法计算出您想要它做什么,因为通常没有办法让它发生。解决这个问题的唯一方法是完全指定您想要调用的特征,但在这一点上,您已经失去了这种方法的任何可能的人体工程学优势。

只需定义并实现您自己的特征,而不是尝试使用 Fn* 特征。我对 avoid/fix 个有问题的方面提出了一些问题。

struct Clf {
    x: f64,
}

trait ClfExt<T: ?Sized> {
    type Result;
    fn classify(&self, arg: &T) -> Self::Result;
}

impl ClfExt<f64> for Clf {
    type Result = bool;
    fn classify(&self, arg: &f64) -> Self::Result {
        *arg > self.x
    }
}

impl ClfExt<[f64]> for Clf {
    type Result = Vec<bool>;
    fn classify(&self, arg: &[f64]) -> Self::Result {
        arg.iter().map(|v| self.classify(v)).collect()
    }
}

fn main() {
    let c = Clf { x: 0.0 };
    let v = vec![-1.0, 0.5, 1.0];
    println!("{}", c.classify(&0.5f64));
    println!("{:?}", c.classify(&v[..]));
}

如何使用 Fn* 特征

为了完整起见,我将其包括在内; 实际上不要这样做。它不仅不受支持,而且该死的丑陋。

#![feature(fn_traits, unboxed_closures)]

#[derive(Copy, Clone)]
struct Clf {
    x: f64,
}

impl FnOnce<(f64,)> for Clf {
    type Output = bool;
    extern "rust-call" fn call_once(self, args: (f64,)) -> Self::Output {
        args.0 > self.x
    }
}

impl<'a> FnOnce<(&'a [f64],)> for Clf {
    type Output = Vec<bool>;
    extern "rust-call" fn call_once(self, args: (&'a [f64],)) -> Self::Output {
        args.0
            .iter()
            .cloned()
            .map(|v| FnOnce::call_once(self, (v,)))
            .collect()
    }
}

fn main() {
    let c = Clf { x: 0.0 };
    let v = vec![-1.0, 0.5, 1.0];

    // Before 1.33 nightly
    println!("{}", FnOnce::call_once(c, (0.5f64,)));
    println!("{:?}", FnOnce::call_once(c, (&v[..],)));

    // After
    println!("{}", c(0.5f64));
    println!("{:?}", c(&v[..]));
}

简短的回答是:你不能。至少它不会按照你想要的方式工作。我认为证明这一点的最好方法是走一遍看看会发生什么,但一般的想法是 Rust 不支持函数重载。

对于此示例,我们将实施 FnOnce,因为 Fn 需要 FnMut,而 FnMut 需要 FnOnce。所以,如果我们要对这一切进行排序,我们可以对其他功能特征进行排序。

首先,这是不稳定的,所以我们需要一些功能标志

#![feature(unboxed_closures, fn_traits)]

然后,让我们做 impl 来获得 f64:

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

Fn 特征家族的参数是通过元组提供的,所以这就是 (f64,) 语法;这是一个只有一个元素的元组。

一切都很好,我们现在可以做 c(0.5),尽管在我们实现其他特征之前它会消耗 c

现在让我们对 Vecs 做同样的事情:

impl FnOnce<(Vec<f64>,)> for Clf {
    type Output = Vec<i32>;
    extern "rust-call" fn call_once(self, args: (Vec<f64>,)) -> Vec<i32> {
        args.0
            .iter()
            .map(|&f| if f > self.x { 1 } else { 0 })
            .collect()
    }
}

Rust 1.33 nightly, you cannot directly call c(v) or even c(0.5) (which worked before); we'd get an error about the type of the function not being known. Basically, these versions of Rust didn't support function overloading. But we can still call the functions using fully qualified syntax 之前,其中 c(0.5) 变为 FnOnce::call_once(c, (0.5,))


不知道你的大局,我只想通过给 Clf 两个函数来解决这个问题,如下所示:

impl Clf {
    fn classify(&self, val: f64) -> u32 {
        if val > self.x {
            1
        } else {
            0
        }
    }

    fn classify_vec(&self, vals: Vec<f64>) -> Vec<u32> {
        vals.into_iter().map(|v| self.classify(v)).collect()
    }
}

那么你的用法示例就变成了

let c = Clf { x: 0 };
let v = vec![-1, 0.5, 1];
println!("{}", c.classify(0.5));   // prints 1
println!("{}", c.classify_vec(v)); // prints [0, 1, 1]

我实际上想制作第二个函数 classify_slice 并使 &[f64] 更通用一些,然后您仍然可以通过引用它们将其与 Vec 一起使用: c.classify_slice(&v).

这确实是可能的,但你需要一个新的特征和一堆乱七八糟的东西。

如果从抽象开始

enum VecOrScalar<T> {
    Scalar(T),
    Vector(Vec<T>),
}

use VecOrScalar::*;

您想要一种使用类型转换的方法

T      (hidden) -> VecOrScalar<T> -> T      (known)
Vec<T> (hidden) -> VecOrScalar<T> -> Vec<T> (known)

因为这样你就可以采用 "hidden" 类型 T,将其包装在 VecOrScalar 中并用 match 提取真实类型 T

你也想要

T      (known) -> bool      = T::Output
Vec<T> (known) -> Vec<bool> = Vec<T>::Output

但是没有高等类型,这有点棘手。相反,你可以做

T      (known) -> VecOrScalar<T> -> T::Output
Vec<T> (known) -> VecOrScalar<T> -> Vec<T>::Output

如果您允许一个可能出现恐慌的分支。

因此特征将是

trait FromVecOrScalar<T> {
    type Output;

    fn put(self) -> VecOrScalar<T>;

    fn get(out: VecOrScalar<bool>) -> Self::Output;
}

有实现

impl<T> FromVecOrScalar<T> for T {
    type Output = bool;

    fn put(self) -> VecOrScalar<T> {
        Scalar(self)
    }

    fn get(out: VecOrScalar<bool>) -> Self::Output {
        match out {
            Scalar(val) => val,
            Vector(_) => panic!("Wrong output type!"),
        }
    }
}
impl<T> FromVecOrScalar<T> for Vec<T> {
    type Output = Vec<bool>;

    fn put(self) -> VecOrScalar<T> {
        Vector(self)
    }

    fn get(out: VecOrScalar<bool>) -> Self::Output {
        match out {
            Vector(val) => val,
            Scalar(_) => panic!("Wrong output type!"),
        }
    }
}

你喜欢的类型

#[derive(Copy, Clone)]
struct Clf {
    x: f64,
}

先实现两个分支:

impl Clf {
    fn calc_scalar(self, f: f64) -> bool {
        f > self.x
    }

    fn calc_vector(self, v: Vec<f64>) -> Vec<bool> {
        v.into_iter().map(|x| self.calc_scalar(x)).collect()
    }
}

然后它将通过为 T: FromVecOrScalar<f64>

实现 FnOnce 来调度
impl<T> FnOnce<(T,)> for Clf
where
    T: FromVecOrScalar<f64>,
{

有类型

    type Output = T::Output;
    extern "rust-call" fn call_once(self, (arg,): (T,)) -> T::Output {

dispatch 首先将私有类型框起来,所以你可以用 enum 提取它,然后 T::gets 结果,再次隐藏它。

        match arg.put() {
            Scalar(scalar) => T::get(Scalar(self.calc_scalar(scalar))),
            Vector(vector) => T::get(Vector(self.calc_vector(vector))),
        }
    }
}

然后,成功:

fn main() {
    let c = Clf { x: 0.0 };
    let v = vec![-1.0, 0.5, 1.0];
    println!("{}", c(0.5f64));
    println!("{:?}", c(v));
}

由于编译器可以看穿所有这些错误,它实际上编译出与直接调用 calc_ 方法基本相同的程序集。

这并不是说写起来很好。像这样重载是一种痛苦、脆弱,而且肯定是一个坏主意™。不要这样做,尽管知道你可以这样做很好。

您可以使用夜间和不稳定的功能:

#![feature(fn_traits, unboxed_closures)]
struct Clf {
    x: f64,
}

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

impl FnOnce<(Vec<f64>,)> for Clf {
    type Output = Vec<i32>;
    extern "rust-call" fn call_once(self, args: (Vec<f64>,)) -> Vec<i32> {
        args.0
            .iter()
            .map(|&f| if f > self.x { 1 } else { 0 })
            .collect()
    }
}

fn main() {
    let c = Clf { x: 0.0 };
    let v = vec![-1.0, 0.5, 1.0];
    println!("{:?}", c(0.5));

    let c = Clf { x: 0.0 };
    println!("{:?}", c(v));
}