預備課:十分鐘讀懂 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 程式碼了。