我希望函数有类型同义词,这样它们就不那么杂乱了.例如,我想要这样的东西:

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个关键字,但类型别名不能用作特征.

类似这样的东西在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)

A less-toy-example use-case:同义词需要使用FnMut,因为我正在try 在nom's Parser的基础上创建同义词.

推荐答案

彼得·霍尔的回答很好地解释了拉斯特和哈斯克尔在类型系统上的区别.他在这方面的知识要多得多,所以我会指出他的答案来解释这一点.相反,我想给你一种实用的方法,让你可以在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的调用以及对其返回的闭包的调用,这可以使这种抽象在运行时性能方面"免费"!
  • 我们必须将fstsnd更改为mut才能调用函数,因为底层闭包类型为FnMut.
  • 我们必须将move加到闭包中,以便闭包获得fstsnd的所有权,否则它将try borrow 返回值中的函数局部变量,这将无法工作.

(Playground)

Rust相关问答推荐

如何在rust中有条件地分配变量?

如何从接收&;self的方法克隆RC

如何将元素添加到向量并返回对该元素的引用?

当两者都有效时,为什么Rust编译器建议添加';&;而不是';*';?

rust 迹-内存管理-POP所有权-链表

为什么Rust函数的移植速度比C++慢2倍?

支持TLS的模拟HTTP服务器

如何模拟/创建ReqData以测试Actix Web请求处理程序?

为什么不';t(&;mut-iter).take(n)取得iter的所有权?

了解Rust';s特征对象和不同函数签名中的生存期注释

类型生命周期绑定的目的是什么?

为什么 js_sys Promise::new 需要 FnMut?

当在lambda中通过引用传递时,为什么会出现终身/类型不匹配错误?

有什么办法可以追踪泛型的单态化过程吗?

如何刷新 TcpStream

具有在宏扩展中指定的生命周期的枚举变体数据类型

如何使用 rust bindgen 生成的 std_vector

有没有办法隐藏类型定义?

将 (T, ()) 转换为 T 安全吗?

在 Rust 中有条件地导入?