彼得·霍尔的回答很好地解释了拉斯特和哈斯克尔在类型系统上的区别.他在这方面的知识要多得多,所以我会指出他的答案来解释这一点.相反,我想给你一种实用的方法,让你可以在Rust内完成你想要的东西.
与其他常见语言相比,Rust的一个非常不同的地方是,Rust中的特征可以在几乎任何类型上实现,包括您的机箱没有定义的类型.这允许您声明一个特征,然后在任何其他对象上实现它,这与只能在您定义的类型上实现接口的语言不同.这给了你令人难以置信的大量自由,需要一些时间来充分把握这种自由赋予你的潜力.
因此,虽然您不能为特征创建别名,但您可以创建自己的特征,该特征在实现其他特征的任何东西上自动实现.这在语义上是不同的,但它最终是close enough,在大多数情况下,您可以像使用别名一样使用它.
trait MyType<T, V>: FnMut(T) -> (T, V) {}
这将使用相同的泛型参数声明特征MyType
,但要求实现此特征的任何对象也必须实现闭包特征.这意味着当编译器看到实现MyType<T, V>
的东西时,它知道它也实现了闭包上层特征.这一点很重要,这样您才能实际调用该函数.
这是解决方案的一半,但现在我们需要MyType
个才能在实现闭包特性的任何对象上实现.这很容易做到:
impl<F, T, V> MyType<T, V> for F
where F: FnMut(T) -> (T, V) {}
所以现在我们有了一个特征:
- 只能在还实现了所需的闭包特性的事物上实现,这意味着也实现了闭包特性.
- 在实现所需闭包特性的所有对象上自动实现.
这是等式的两个方面,使MyType<T, V>
和FnMut(T) -> (T, V)
effectively相等,即使它们实际上在类型系统中不是相同的特征.它们不是相同的特征,但你可以互换使用它们.
现在,我们可以围绕我们的新特征调整compose
的定义:
fn compose<T, U, V>(
mut fst: impl MyType<T, U>,
mut snd: impl MyType<U, V>,
) -> impl MyType<T, V> {
move |x| {
let (t, u) = fst(x);
let (_, v) = snd(u);
(t, v)
}
}
这里有几个重要的变化:
- 我们使用
impl MyType<_, _>
,这样函数就可以接收实现您的特征的任何内容,包括您try 针对的闭包类型.请注意,没有dyn
,这也意味着没有动态调度.这消除了一定程度的间接性.
- 我们还返回return
impl MyType<_, _>
,这意味着我们可以返回闭包,而无需对其进行装箱,这既防止了不必要的堆分配,也防止了不必要的间接级别.
- 由于前面两点,编译器可能会完全内联对
compose
的调用以及对其返回的闭包的调用,这可以使这种抽象在运行时性能方面"免费"!
- 我们必须将
fst
和snd
更改为mut
才能调用函数,因为底层闭包类型为FnMut
.
- 我们必须将
move
加到闭包中,以便闭包获得fst
和snd
的所有权,否则它将try borrow 返回值中的函数局部变量,这将无法工作.
(Playground)