[英]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 特征的类型参数(也包括FnOnce
和FnMut
)不稳定。
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",
};
}
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.