![](/img/trans.png)
[英]How and when to use the generic type parameter in impl block in Rust?
[英]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 特征的类型参数(也包括FnOnce
和FnMut
)不稳定。
尝试像这样编写特征绑定:
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.