初学 Rust

早就想学 Rust 了,但一直咕着。这次小学期的程设训练开了 Rust 课堂,就毫不犹豫地选了,也以此为契机学了 Rust。

这篇就是简单记录一下大体上的学习过程,如果有什么心得体会、经验分享之类应该会开新的文章。

顺便也算是程设训练的游记了(“游记”这个词来源于 OI 时期的习惯)。

初学 —— The Rust Programming Language

不同于 Python、JavaScript,这次学 Rust 更像是当初学 C++,不是先魔改代码、用啥搜啥、StackOverflow,而是在还没怎么接触过时就直接看书。

虽然没有细看中文翻译的质量如何,但至少术语是不好翻译的,就直接看了英文版的 The Rust Programming Language

这本书看下来给我的感觉是,很多地方没有按知识点依赖关系的拓扑排序来讲,而似乎是以某种由浅入深的顺序来的,很多后面才学到的概念在前文就出现,而前面学的概念的完全不需要后面知识的扩展又在后面才补充,还有一些零碎的知识是在 project chapter 讲的。

反正,每天看两三章,还能有不少时间摸鱼,摸个 一两周就看得差不多了(

初步练习 —— 洛谷

看书的同时在 洛谷 上随便挑了点入门题和板子题写。只不过一开始没学 io 之类的,会再多语法也做不动题,但幸好有 GitHub Copilot

在这放个读入的例子:

6 0
1 1 4 5 1 4
fn main() {
    let stdin = std::io::stdin();
    let mut input = String::new();

    stdin.read_line(&mut input).unwrap();
    let mut parts = input.split_whitespace();
    let n = parts.next().unwrap().parse::<u32>().unwrap();
    assert_eq!(n, 6);
    let m = parts.next().unwrap().parse::<u32>().unwrap();
    assert_eq!(m, 0);

    input.clear();
    stdin.read_line(&mut input).unwrap();
    let a = input
        .split_whitespace()
        .map(|x| x.parse::<i32>().unwrap())
        .collect::<Vec<_>>();
    assert_eq!(a, vec![1, 1, 4, 5, 1, 4]);
}

复习 —— A half-hour to learn Rust

之前在 Hacker News 上看到了 A half-hour to learn Rust 这篇文章。如果真的是看这个东西来学 Rust,不说能不能学会,肯定是学不扎实的。但是,看完书之后很多语法也忘了,看这个用来复习还是非常不错的。(另外,我感觉这个的顺序貌似比书更有条理一些,虽然初学不一定友好 🤔)

深入了解 —— The Rust Standard Library

其实看书的时候就感觉到了,很多时候不懂一段代码不是不懂语法,而是不懂 标准库 的实现,而把 API 理解成了没学过的语法。了解标准库,不仅是提升编码和运行的效率,也是能够看懂很多基础代码的关键。

看文档的时候发现,不仅是学到了很多有用的 methods,也学到了很多其它东西。比如说:

  • 一般来说都可以用 std::cmp::max 来取 max,但 f32 专门有一个 pub fn max(self, other: f32) -> f32,这是因为浮点数有 NaN 这个特殊情况导致 f32 没有 impl Ord,而 std::cmp::max 需要 Ord
  • 书里貌似没讲到的 keyword ref(以及在 pattern matching 中和 & 的区别)
  • 引用之间进行比较时会自动转成指向的值,即实际进行比较的是指向的值(可以通过 std::ptr::eq 来比较地址)
  • 从标准库的 API 设计能领会到很多 trait 和 generic 的用法,体会到它们组合在一起的强大
  • Iterator 自动有 IntoIterator,所以用 IntoIterator 代替 Iterator 作为 trait bound 可以让函数更灵活
  • ……

简单看一看就能知道 the book 里涉及到的真的只是冰山一角,学 Rust 标准库的意义会比学 C++ 的 STL 大得多(个人感觉)(也可能是我 C++ 水平太低 / 没认真学 STL 😢)(或者 Primer Plus 讲的全一些)。

上课

Class 1

因为自学过了,所以上课就基本上是把老师的声音当作 bgm 偶尔听一下,然后继续看标准库文档(

上课的时候老师提到 Rust 编译器可以提示如何修改,于是我顺势在课程群里发了个 rust 程序员现状 的截图(

rust程序员现状

课后把 OJ 上的作业速通后发现榜不是公开的 😢

后来还发现自己一道题写了个 collect 得到的 Vec 只用在一个 for 里(就是说可以直接把迭代器用在 for 不用先 collect)😵 想改过来,但虽然没有公开榜,还是不想承受可能的罚时(

Class 2

第二节课前后共一天多的时间,写了 16h 左右,把 Wordle 大作业的基础功能写完了。(本来以为 Wordle 挺好写的,没想到需求这么多,需求文档模糊不清的地方还有的要问有的要自己设计。

感觉课上讲的又快,大作业需要的知识又没讲全,很难想象如果不自学该怎么应对这个课(

Class 3

修了一些文档说明不清导致的 bug,然后研究了一下扩展功能怎么写。

不知道为什么把单词按信息熵排序写了半天,明明挺简单的。可能是听着老师讲课没法专心想算法。

后来试着用 rayon 把信息熵计算并行化了,在我本机除了第一次猜测基本上都能秒出结果,感觉很爽 ,甚至玩了半天意义不明的照着提示输入

再后来发现用 release 模式编译的优化比并行还大 🌚

第一次大作业验收

验收在 THUWC 去过的东主楼,但上大学后还是第一次去,结果走错了,迟到了 5min 😵 然后非常慌张地展示提高功能,都没太演示全,感觉白写了

Class 4~8

略(

第二次大作业验收

这次真的有比 pretests 强很多的 system test 了,还好没 FST(助教:这个点你为什么能过啊(x

可能是因为这个课不是学前端的,而且助教只简单看了一下界面演示没看代码,槽点很多的前端被夸了很科学(

可能是因为这个课不是学数据库的,而且听说有其他同学数据库里全是 JSON,普普通通的建表被夸了很科学(

关于课上的一些翻译

为什么什么都要翻译出来啊(虽然如果英语母语的话看原文应该也很尬 🤣

trait: 特型;panic: 恐慌;poisoned: 中毒;crate: 箱;library crate: 库箱

Async Rust

先是看了 Asynchronous Programming in Rust,感觉看得一知半解的(尤其是 Pin),好像大概知道 Future 在干什么又不完全知道(而且这本书好咕啊,TODO 的章节应该是有生之年了

没看完 async book,直接去看 Tokio TutorialAsync programming in Rust with async-std 了。作为 async runtime 的教程,它们涉及到的具体原理和实现少一些,更注重怎么实际使用,读起来会容易理解一些。因为 Tokio 更 popular,主要看的是 Tokio 的教程。

数据库: Diesel

感觉 diesel 的教程 写的挺简略的,但对着 examples 硬查文档也勉强能看懂基本用法。ORM 看着就很“安全”,只不过实在是太类型体操了,不仅文档查起来有点小麻烦,代码复用也经常因为繁琐的 trait bounds 写不太动(也可能是我没学会)。

diesel-derive-enum 是好用的。

Sqlite 的 RETURNING 语句需要启用 returning_clauses_for_sqlite_3_35 feature flag(并且需要至少 3.35 版本的 Sqlite)。没启用的时候对着一堆 trait bound 不满足的错误信息(就和 C++ 模板感觉差不多了..)根本发现不了错误原因,还是翻文档翻半天发现的。

还遇到一个 Sqlite 锁死的坑,通过 使用 r2d2 设定 busy_timeout使用 WAL mode 以及 immediate transaction (hopefully)解决了。