我希望函数有类型同义词,这样它们就不会那么杂乱。例如,我希望这样:
type MyType<T, V> = FnMut(T) -> (T, V);
fn compose<T, U, V>(fst: MyType<T, U>, snd: MyType<U, V>) -> MyType<T, V> {
|mut& x| {
let (t, u) = fst(x);
let (_, v) = snd(u);
(t, v)
}
}
但是编译失败,我可以添加dyn
关键字,但是类型别名不能作为traits。
在Haskell
中可以执行类似的操作:
type MyType a b = a -> (a, b)
compose :: MyType a b -> MyType b c -> MyType a c
compose f g = \x ->
let (a, b) = f x
(_, c) = g b
in (a, c)
- 一个不太像玩具的用例 *:同义词需要使用
FnMut
,因为我正试图基于nom
的Parser
来创建同义词。
2条答案
按热度按时间zzzyeukh1#
Peter Hall的回答很好地解释了Rust和Haskell之间类型系统的差异。他对此更有知识,所以我将指出他的答案来解释这一点。相反,我想给你一个实用的方法,你可以在Rust中完成你想要的。
Rust与其他通用语言的一个显著不同之处在于,Rust中的trait可以在几乎任何类型上实现,包括crate没有定义的类型。这允许你声明一个trait,然后在其他任何类型上实现它,这与那些只能在你定义的类型上实现接口的语言形成了鲜明的对比。这给了你难以置信的大量自由。要完全掌握这种自由赋予你的潜力需要一些时间。
因此,虽然你不能为trait创建别名,但你可以创建自己的trait,它会自动在任何实现其他trait的东西上实现,虽然语义上不同,但它最终与同一个东西"足够接近",在大多数情况下,你可以像别名一样使用它。
这声明了trait
MyType
,带有相同的泛型参数,但是要求任何实现这个trait的东西都必须实现闭包trait,这意味着当编译器看到一些实现MyType<T, V>
的东西时,它知道它也实现了闭包超trait,这一点很重要,这样你才能真正调用这个函数。这只是解决方案的一半,但是现在我们需要在任何实现闭包特性的东西上实现
MyType
。所以现在我们有了一个特征:
这是使
MyType<T, V>
和FnMut(T) -> (T, V)
* 有效 * 等价的等式的两个方面,即使它们在类型系统中实际上不是相同的特性,它们不是相同的特性,但是你可以 * 几乎 * 互换地使用它们。现在,我们可以围绕新特性调整
compose
的定义:这里有几个重要的变化:
impl MyType<_, _>
,这样函数就可以接收任何实现trait的东西,包括你试图指向的闭包类型。注意,没有dyn
,这也意味着没有动态分派。这就消除了一层间接性。impl MyType<_, _>
,这意味着我们可以返回一个闭包而不对其进行装箱,这既防止了不必要的堆分配,也防止了不必要的间接层。compose
的调用以及对它返回的闭包的调用,这可以使这种抽象在运行时性能方面"免费"!fst
和snd
更改为mut
,因为底层闭包类型是FnMut
。move
添加到闭包中,以便闭包获得fst
和snd
的所有权,否则它将试图在返回值中借用函数局部变量,这将无法工作。(Playground)
bkkx9g8r2#
Rust中的函数表达方式与Haskell中的不同。
在Haskell中,函数签名对应于类型。具有相同签名的两个函数具有相同的类型,即使它们的实现方式不同,即使它们是不同环境下的闭包。类型纯粹是从函数的参数和返回值派生的。
在Rust中,可命名函数类型只有一个“类”,即
fn
。这些是没有环境的函数 * 指针 *。闭包总是不同的类型,即使它们有相同的参数和返回类型。这只是语言中的一种权衡。在RustFn
中,FnOnce
和FnMut
是 traits 而不是类型。并且不能为trait创建别名。Rust中有一个针对trait aliases的RFC,它可能部分满足了你的需求,但这一情况自2017年以来一直在持续,似乎还没有稳定下来。