Подготовка: читаем 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 build --release, если используешь cargo.

Твое упражнение

  1. Установи Rust (rustup.rs), сохрани программу и запусти. Сверь вывод.
  2. Добавь пятую цену и сделай так, чтобы программа печатала еще и худшее отношение.
  3. Напиши функцию max_of(v: &Vec<f64>) -> f64 и выведи ее результат.
  4. Собери цикл в 300 миллионов шагов с -O и без и сравни время.
  5. Попроси агента объяснить простыми словами первую ошибку компилятора, которую встретишь.

Что дальше

Теперь можно читать код на Rust из занятия 13.