Prerequisite: Reading Rust in Ten Minutes

A short from-zero lesson for the arbitrage course. Variables, loops, vectors and functions in Rust through a small program that reads four prices, and why release builds are faster. Runnable code with output.

A helper lesson for the course "Build your own arbitrage bot". Lesson 13 moves the hot loop of the scanner into Rust. You do not need to become a Rust programmer, only to read a short program and run it.

The head-on way: stay in Python

Python is fine for the first scanner. The trouble starts when one function must run millions of times a second: the interpreter does extra work for every step. A compiled language such as Rust turns the program into machine code once, before it runs, and the loop then costs almost nothing per step. The price is that you write a little more and the compiler is strict.

A small program

The same four prices as in the previous primer. Save this as 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));
}

Build and run it: 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

How to read it

  • fn name(...) -> type { ... } defines a function. The last expression without a semicolon is what it returns: sum / v.len() as f64 gives the mean.
  • let makes a variable. It cannot change unless you write let mut. That is why sum and best have mut.
  • Vec<f64> is a list of decimal numbers (a vector). vec![...] creates one. v.len() is its length.
  • for i in 0..n loops i from 0 up to n minus 1. for x in v loops over the items.
  • &prices means "lend the vector to the function, do not give it away". You can skip the details for now: if the compiler complains about ownership, show the message to your agent.
  • println! prints. {} is a place for a value, {:.4} is a decimal number with 4 digits after the point.
  • Types are written down (f64), and the compiler refuses a program where they do not fit. It feels strict, but it catches mistakes before the program runs.

Why the flag -O matters

Without -O Rust builds a slow version for debugging. With it, the compiler optimizes. A loop of a billion steps on one machine:

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

(Your numbers will differ.) Always measure a release build, the one made with -O, or with cargo build --release if you use cargo.

Your exercise

  1. Install Rust (rustup.rs), save the program and run it. Match the output.
  2. Add a fifth price and make the program print the worst ratio too.
  3. Write a function max_of(v: &Vec<f64>) -> f64 and print its result.
  4. Build with and without -O a loop of 300 million steps and compare the time.
  5. Ask your agent to explain the first compiler error you meet, in plain words.

What comes next

You can read the Rust code of lesson 13 now.