简体   繁体   English

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

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

If functions are traits, only one type parameter should be sufficient to get the function type with arguments and return type.如果函数是特征,则只需一个类型参数就足以获得具有 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
}

I could do it with 2 type parameters, but is it possible to do it with just one?我可以用 2 个类型参数来做到这一点,但是否可以只用一个来做到这一点? Can I get the inputs as well?我也可以得到输入吗?

No, it is not possible to leave the output type unspecified in the trait bound.不,不可能在特征绑定中未指定 output 类型。 Although Fn::Output is defined as an associated type for that trait, the compiler has an explicit safeguard against the standard trait bound syntax because the type parameters of function traits (including also FnOnce and FnMut ) are not stabilized .尽管Fn::Output被定义为该特征的关联类型,但编译器对标准特征绑定语法有明确的保护措施,因为 function 特征的类型参数(也包括FnOnceFnMut不稳定

Attempting to write the trait bound like this:尝试像这样编写特征绑定:

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

Results in this error:导致此错误:

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

The function-based syntax for trait bounds also not provide a way to leave the output type unspecified.特征边界的基于函数的语法也没有提供一种不指定 output 类型的方法。

So yes, you need two type parameters.所以是的,你需要两个类型参数。 But this is very unlikely to be an issue in practice.但这在实践中不太可能成为问题。 The compiler will take care of inferring the second type parameter for you.编译器将负责为您推断第二个类型参数。

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",
    };
}

Playground 操场

There are a couple obstacles to doing this.这样做有几个障碍。 The first is that direct use of the Fn traits themselves is unstable behind the fn_traits feature , so getting the concrete type of the argument list requires the nightly compiler.首先是直接使用Fn特征本身在fn_traits特性之后是不稳定的,因此获取参数列表的具体类型需要夜间编译器。 For example, the current (unstable!) representation of the arguments for Fn(u32) -> String is (u32,) .例如,对于Fn(u32) -> String的 arguments 的当前(不稳定!)表示是(u32,)

That doesn't solve the primary issue, though.不过,这并不能解决主要问题。 The Fn family of traits has Output as an associated type, but the argument list is a generic parameter: Fn特征家族具有Output作为关联类型,但参数列表是通用参数:

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

In other words, a type may provide different implementations of Fn depending on the types of the arguments it's called with, and each implementation may have a different output type .换句话说,根据调用它的 arguments 的类型,一个类型可能提供不同的Fn实现,并且每个实现可能有不同的 output 类型 Here's an example with two implementations of FnOnce for a single type:下面是一个针对单一类型的两个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,)));
}

This outputs:这输出:

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

Given that an implementor of FnOnce is (from the trait's perspective) callable with multiple argument list types, and each implementation can output a different type, you must disambiguate the argument list type so the compiler can resolve which trait implementation (and thus which output type) to use.鉴于FnOnce的实现者(从 trait 的角度来看)可以用多个参数列表类型调用,并且每个实现都可以 output 不同的类型,您必须消除参数列表类型的歧义,以便编译器可以解析哪个 trait 实现(以及哪个 output 类型) 使用。


Existing crates that abstract over functions have typically worked around the instability of the Fn traits by defining their own trait for some subset of Fn implementors, like:现有的对函数进行抽象的 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)
    }
}

Generally these implementations are macro-generated to avoid having to write implementations for every number of arguments ( () , (A,) , (A, B) , ...).通常,这些实现是宏生成的,以避免必须为每个数量的 arguments ( () , (A,) , (A, B) , ...) 编写实现。

With our new custom trait, we can write this:使用我们新的自定义特征,我们可以这样写:

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

And the compiler can correctly infer the output type.并且编译器可以正确推断出 output 类型。 ( playground link ) 游乐场链接

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

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