预备课:十分钟读懂 Rust

套利课程的从零开始短课。通过一个读取四个价格的小程序,讲解 Rust 的变量、循环、向量和函数,以及为什么发布版本更快。代码可运行,附有输出。

这是课程"做你自己的套利机器人"的辅助课。第 13 课把扫描器的热点循环搬到 Rust 里。你不需要成为 Rust 程序员,只要能读一小段程序并运行它。

直接的办法:继续用 Python

第一个扫描器用 Python 没问题。麻烦从一个函数每秒要运行数百万次时开始:解释器每一步都要多做工作。像 Rust 这样的编译型语言,在运行前一次性把程序变成机器码,之后循环的每一步几乎不花什么。代价是要多写一点,而且编译器很严格。

一个小程序

还是上一篇预备课里的那四个价格。保存为 r.rs:

fn mean(v: &Vec<f64>) -> f64 {
    let mut sum = 0.0;
    for x in v {
        sum += x;
    }
    sum / v.len() as f64
}

fn main() {
    let prices: Vec<f64> = vec![100.0, 101.5, 99.8, 102.0];
    let mut best = 0.0;
    for i in 0..prices.len() - 1 {
        let ratio = prices[i + 1] / prices[i];
        println!("step {}: ratio {:.4}", i, ratio);
        if ratio > best {
            best = ratio;
        }
    }
    println!("best {:.4}, mean price {:.3}", best, mean(&prices));
}

编译并运行:rustc -O r.rs -o r && ./r。

step 0: ratio 1.0150
step 1: ratio 0.9833
step 2: ratio 1.0220
best 1.0220, mean price 100.825

怎么读

  • fn name(...) -> type { ... } 定义一个函数。最后一个不带分号的表达式就是它的返回值:sum / v.len() as f64 返回平均值。
  • let 创建变量。除非写成 let mut,否则它不能改变。所以 sum 和 best 带了 mut。
  • Vec<f64> 是一列小数(向量)。vec![...] 创建它。v.len() 是它的长度。
  • for i in 0..n 让 i 从 0 循环到 n 减 1。for x in v 遍历每一项。
  • &prices 的意思是"把向量借给函数,不要送出去"。细节现在可以先跳过:如果编译器抱怨所有权问题,把提示信息给你的智能体看。
  • println! 用来打印。{} 是放值的位置,{:.4} 是保留小数点后 4 位的小数。
  • 类型是明确写出来的(f64),类型对不上编译器就拒绝这个程序。这感觉很严格,但能在程序运行前抓出错误。

为什么标志 -O 很重要

不加 -O,Rust 会构建一个用于调试的慢版本。加上它,编译器会做优化。在一台机器上跑十亿步的循环:

without -O: 1.18 s
with -O:    0.39 s

(你的数字会不同。)一定要测量发布版本,也就是用 -O 构建的版本;如果用 cargo,就用 cargo build --release。

你的练习

  1. 安装 Rust(rustup.rs),保存程序并运行。对照输出。
  2. 加上第五个价格,让程序也打印最差的比值。
  3. 写一个函数 max_of(v: &Vec<f64>) -> f64 并打印它的结果。
  4. 用 -O 和不用 -O 分别构建一个 3 亿步的循环,比较耗时。
  5. 让你的智能体用通俗的话解释你遇到的第一个编译器错误。

下一步

现在可以读第 13 课的 Rust 代码了。