Rust小记

这些是我在使用Rust的过程中萌生的想法,也可能是遇到的问题。不必深究,仅图一乐。

GATs

这个问题是我在入门函数式时遇到的问题。

Rust对函数式的支持属于较好的一方,你能在大量的结构上找到map等操作。但是到目前为止,Rust并不是完全支持函数式编程,而问题之一就是GATs的草案至今还未落实到正式版。

The push for GATs stabilization | Rust Blog

函数式编程我想并不是这一小节需要重点介绍的内容,也许一篇名为《Functor, Applicative and Monad》的文章能够帮你学到一些东西。文章中介绍了函数式编程中几个重要的概念

  • Functor
  • Applicative
  • Monad

简单的说,Functor对应着map操作,Applicative对应着apply操作,而Monad对应了flatMap操作。在编写这些代码时,就会发现相同的内容在Rust里遇到了麻烦。例如你无法在trait里定义统一签名的map:

1
2
3
4
5
6
trait Functor {
type T;
fn map<F>(self, f: F) -> Self
where
F: FnMut(Self::T) -> /* ? */;
}

以Rust现有的类型系统,你无法填写代码中的留空位置,否则会写出类似于

1
2
3
4
5
6
7
trait Functor {
type T;
type Wrap<R>:Functor; // 离谱
fn map<F,R>(self, f: F) -> Self::Wrap
where
F: FnMut(Self::Functor<T>) -> R;
}
  • 函数F的返回值是不确定的R
  • 函数F的返回值R的取值来自一个高阶类型Wrap的内部类型R
  • Wrap需要实现Functor

这个也许才是Rust中原原本本的Functor。先不说在Rust的语法下它抽象得离谱,最可惜的是它现在无法在正式版中通过编译——你家Rust暂时写不了这种东西。

作为拓展,一个更加麻烦的事情是GATs中我完全可以令Wrap<R>是另一个实现Functor的奇怪类型,从而破坏掉Functor的性质。在trait里Wrap和Self之间没有明显关系。所以若是没有新的语法引入,就永远别想在Rust里写Haskell那样的函数式。

但是Rust本来就和Haskell不是一门语言,是吧?

同样的问题发生在Rust的Future中。(假设)我们都知道在Rust中async关键字就是个长得像下面代码的糖

1
2
async fn func() -> R
fn func() -> impl Future<Output = R>

那对于下方的代码,就会有

1
2
3
4
5
6
trait Shiro{
async fn find_clothe(&self) -> Clothe;
}
trait Shiro{
fn find_clothe(&self) -> impl Future<Output = Clothe>;
}

那么在这个时候,async函数将会把self的生命周期捕捉,这意味着impl Future<Output = Clothe>受到其生命周期的限制,就有代码

1
2
3
4
trait Shiro{
type RealR<'a> : Future<Output = Clothe> + 'a;
fn find_clothe(&self) -> RealR<'_>;
}

GATs又一次出现😩。不过trait里没法写async的原因不止于此,在此不展开了。

暴露问题而非引入

很多人觉得Rust学习难度较高,尤其认为Rust中的生命周期和移动语义实在费解,就将Rust定为难学的语言。我个人则认为Rust没有引入新的问题,Rust只是将始终存在的周期和移动语义暴露在了阳光下。

你觉得在 C++ 中没有这些,但这只是 C++ 的设计哲学Zero-overhead帮了你。C++ 的东西实在是太多太多了,所以它认为不知道的东西不应成为负担。所以,不知道新的 C++ 标准也能把 C++ 当 C+ 类来写,不知道智能指针那就用指针,不知道左右值那就不用考虑move语义,不知道元编程那就不用碰模板,不知道编译期计算那就不用碰concept,不知道内存管理那就不用delete……好吧最后一个有点危险。

如果弄不明白Rust的相关概念,恐怕精进C++也会遇到困难。


Rust小记
https://blog.chenc.me/2021/09/07/rust-note/
作者
CC
发布于
2021年9月8日
许可协议