Rust中函数的类型同义词

mfuanj7w  于 2023-03-08  发布在  其他
关注(0)|答案(2)|浏览(130)

我希望函数有类型同义词,这样它们就不会那么杂乱。例如,我希望这样:

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,因为我正试图基于nomParser来创建同义词。
zzzyeukh

zzzyeukh1#

Peter Hall的回答很好地解释了Rust和Haskell之间类型系统的差异。他对此更有知识,所以我将指出他的答案来解释这一点。相反,我想给你一个实用的方法,你可以在Rust中完成你想要的。
Rust与其他通用语言的一个显著不同之处在于,Rust中的trait可以在几乎任何类型上实现,包括crate没有定义的类型。这允许你声明一个trait,然后在其他任何类型上实现它,这与那些只能在你定义的类型上实现接口的语言形成了鲜明的对比。这给了你难以置信的大量自由。要完全掌握这种自由赋予你的潜力需要一些时间。
因此,虽然你不能为trait创建别名,但你可以创建自己的trait,它会自动在任何实现其他trait的东西上实现,虽然语义上不同,但它最终与同一个东西"足够接近",在大多数情况下,你可以像别名一样使用它。

trait MyType<T, V>: FnMut(T) -> (T, V) {}

这声明了trait MyType,带有相同的泛型参数,但是要求任何实现这个trait的东西都必须实现闭包trait,这意味着当编译器看到一些实现MyType<T, V>的东西时,它知道它也实现了闭包超trait,这一点很重要,这样你才能真正调用这个函数。
这只是解决方案的一半,但是现在我们需要在任何实现闭包特性的东西上实现MyType

impl<F, T, V> MyType<T, V> for F
where F: FnMut(T) -> (T, V) {}

所以现在我们有了一个特征:

  • 只能在同时实现所需闭包特征的对象上实现,这意味着闭包特征也已实现。
  • 在实现所需闭包特征的所有对象上自动实现。

这是使MyType<T, V>FnMut(T) -> (T, V) * 有效 * 等价的等式的两个方面,即使它们在类型系统中实际上不是相同的特性,它们不是相同的特性,但是你可以 * 几乎 * 互换地使用它们。
现在,我们可以围绕新特性调整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<_, _>,这样函数就可以接收任何实现trait的东西,包括你试图指向的闭包类型。注意,没有dyn,这也意味着没有动态分派。这就消除了一层间接性。
  • 我们还 * return * impl MyType<_, _>,这意味着我们可以返回一个闭包而不对其进行装箱,这既防止了不必要的堆分配,也防止了不必要的间接层。
  • 由于前面两点,编译器可以完全内联对compose的调用以及对它返回的闭包的调用,这可以使这种抽象在运行时性能方面"免费"!
  • 为了调用函数,我们必须将fstsnd更改为mut,因为底层闭包类型是FnMut
  • 我们必须将move添加到闭包中,以便闭包获得fstsnd的所有权,否则它将试图在返回值中借用函数局部变量,这将无法工作。

Playground

bkkx9g8r

bkkx9g8r2#

Rust中的函数表达方式与Haskell中的不同。
在Haskell中,函数签名对应于类型。具有相同签名的两个函数具有相同的类型,即使它们的实现方式不同,即使它们是不同环境下的闭包。类型纯粹是从函数的参数和返回值派生的。
在Rust中,可命名函数类型只有一个“类”,即fn。这些是没有环境的函数 * 指针 *。闭包总是不同的类型,即使它们有相同的参数和返回类型。这只是语言中的一种权衡。在Rust Fn中,FnOnceFnMuttraits 而不是类型。并且不能为trait创建别名。
Rust中有一个针对trait aliases的RFC,它可能部分满足了你的需求,但这一情况自2017年以来一直在持续,似乎还没有稳定下来。

相关问题