繁体   English   中英

如何仅使用一个类型参数在 Rust 中定义通用 function

[英]How to use only one type parameter to define a generic function in Rust

如果函数是特征,则只需一个类型参数就足以获得具有 arguments 和返回类型的 function 类型。

struct Hello<F>
where
    F: Fn(), //this does not work, it is seen as Input = () and output = ()
{
    output: F::Output,
    f: F,
}

fn hello_test() {
    let f = || 10i32;
    let h = Hello { f, output: 10 }; //I wanted the output to be inferred as i32
}

我可以用 2 个类型参数来做到这一点,但是否可以只用一个来做到这一点? 我也可以得到输入吗?

不,不可能在特征绑定中未指定 output 类型。 尽管Fn::Output被定义为该特征的关联类型,但编译器对标准特征绑定语法有明确的保护措施,因为 function 特征的类型参数(也包括FnOnceFnMut不稳定

尝试像这样编写特征绑定:

    F: std::ops::Fn<()>,

导致此错误:

error[E0658]: the precise format of `Fn`-family traits' type parameters is subject to change
 --> src/lib.rs:3:8
  |
3 |     F: std::ops::Fn<()>,
  |        ^^^^^^^^^^^^^^^^ help: use parenthetical notation instead: `Fn() -> ()`
  |
  = note: see issue #29625 <https://github.com/rust-lang/rust/issues/29625> for more information

特征边界的基于函数的语法也没有提供一种不指定 output 类型的方法。

所以是的,你需要两个类型参数。 但这在实践中不太可能成为问题。 编译器将负责为您推断第二个类型参数。

struct Hello<F, O>
where
    F: Fn() -> O,
{
    output: O,
    f: F,
}

pub fn hello_test() {
    let f = || 10u32;
    let _h = Hello {
        f,
        output: 10, // output type is inferred correctly
    };

    // specifying the return type is also possible
    let _h2: Hello<_, &str> = Hello {
        f: || "ten",
        output: "ten",
    };
}

操场

这样做有几个障碍。 首先是直接使用Fn特征本身在fn_traits特性之后是不稳定的,因此获取参数列表的具体类型需要夜间编译器。 例如,对于Fn(u32) -> String的 arguments 的当前(不稳定!)表示是(u32,)

不过,这并不能解决主要问题。 Fn特征家族具有Output作为关联类型,但参数列表是通用参数:

//               vvvv
pub trait FnOnce<Args> {
    type Output;
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

换句话说,根据调用它的 arguments 的类型,一个类型可能提供不同的Fn实现,并且每个实现可能有不同的 output 类型 下面是一个针对单一类型的两个FnOnce实现的示例:

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

use std::ops::FnOnce;

struct Foo;

impl FnOnce<(u32,)> for Foo {
    type Output = u32;

    extern "rust-call" fn call_once(self, arg: (u32,)) -> u32 {
        arg.0
    }
}

impl FnOnce<(f32,)> for Foo {
    type Output = f32;

    extern "rust-call" fn call_once(self, arg: (f32,)) -> f32 {
        arg.0
    }
}

fn main() {
    println!("Foo::call(5)   = {:.1}", Foo.call_once((5_u32,)));
    println!("Foo::call(5.0) = {:.1}", Foo.call_once((5.0_f32,)));
}

这输出:

Foo::call(5)   = 5
Foo::call(5.0) = 5.0

鉴于FnOnce的实现者(从 trait 的角度来看)可以用多个参数列表类型调用,并且每个实现都可以 output 不同的类型,您必须消除参数列表类型的歧义,以便编译器可以解析哪个 trait 实现(以及哪个 output 类型) 使用。


现有的对函数进行抽象的 crate 通常通过为Fn实现者的某些子集定义自己的特征来解决Fn特征的不稳定性,例如:

trait CustomFn<Args> {
    type Output;
    fn custom_call(&self, args: Args) -> Self::Output;
}

// Implement CustomFn for all two-argument Fn() implementors
impl<F, A, B, Out> CustomFn<(A, B)> for F
where
    F: Fn(A, B) -> Out
{
    type Output = F::Output;
    fn custom_call(&self, args: (A, B)) -> Self::Output {
        (self)(args.0, args.1)
    }
}

通常,这些实现是宏生成的,以避免必须为每个数量的 arguments ( () , (A,) , (A, B) , ...) 编写实现。

使用我们新的自定义特征,我们可以这样写:

struct Hello<F, A>
where
    F: CustomFn<A>,
{
    // F::Output can be inferred from the argument types
    output: F::Output,
    f: F,
}

fn hello_test() {
    let f = |a: u32, b: u32| a + b;
    let h = Hello { f, output: 10 };
}

并且编译器可以正确推断出 output 类型。 游乐场链接

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM