Rust

These are my notes on learning Rust. There will be comparisons to C and Go, because those are the two languages I know which are similar to Rust and operate in a similar area (systems programming, command line tools etc.)

As I am learning Rust, there may be mistakes or things I've not explained clearly, or I've not covered every single edge case. Please don't barge in and tell me what I've done wrong, or that you disagree with an opinion - that spoils the learning experience (if I want / need help, I will ask for it).

Rust training for FOSS developers

Notes taken from this course:

If anything is wrong in the above, it's my fault.

Toolchain

Rust comes with various tools and commands:

rustc: Compiles source files to an executable, similar to gcc and go build.

rustfmt: Formats sources files in a standard way, similar to gofmt.

Hello world

Hello world is straightforward in Rust:

hello_world.rs

fn main() {
    println!("Hello world!");
}

main function

Like C and Go, Rust will start execution at the main function. Unlike C, it takes no parameters and has no return value.

The ! means that println is a macro rather than a function. These are similar but not the same (C also has macros and functions which are different but are called in the same way).

Rust uses semicolons to terminate most statements, as is the case in C (but not Go).

Visual Studio Code

If you are using VSCode, install the rust-lang.rust-analyzer extension. This is the official extension and will provide better syntax highlighting, type completion, automatic imports etc.

Variables and references

Variables are immutable by default (there are good reasons for the immutability, but calling something a variable when it is immutable is confusing). A variable can be made mutable with the mut keyword, e.g. let mut x = 5;. The Rust toolchain will warn you if you mark a variable as mutable but never change its value. References are also immutable by default.

Constants are immutable but cannot be made mutable. Use const instead of let, e.g. const x = 5;. Constants can only be given a value that is a constant expression that can be evaluated at compile time. This can be a calculation, e.g. const SECONDS_IN_DAY = 60 * 60 * 24;, provided it meets the constant evaluation requirements.

Variables can be shadowed, by creating a new variable with the same name. Most languages do not allow this unless the shadowed variable is in its own block, e.g. the following is not permitted in C:

    int x = 5;
    float x = 10.0;

Rust does allow shadowing, and it is often used when a value is converted from one type to another, but you don't want to use both afterwards (e.g. if you read in a number via standard input it will be a string by default, but you probably want it converted to an integer).

Functions

Associated functions are implemented on types, e.g. String::new. The name new is a convention, it is not special syntax like in some other languages (e.g. new DateTimeImmutable() in PHP).

Passing parameters to functions is tricky to understand at first, especially from a C background. Functions often take parameters as references rather than values, which in C would mean that:

In C, passing a parameter by reference means that the function can modify the original data. References are mutable unless you specify otherwise (usually with const).

In Rust however, if a function only needs to read the parameter, you pass a reference. References are immutable unless you specify otherwise, so you cannot alter the parameter. Effectively it's the opposite way around to C.

Function calls can be chained, with the results of one function passed as the input of the next function.

println!

println! is a macro rather than a function, hence calling it with !. It will print the string provided as an argument to standard output (usually the console), followed by the appropriate new line character(s) for the host operating system.

Placeholders can be used in println as curly braces. These can be a variable:

let x = 1;
println!("x is {x}");

or the result of an expression, in which case the expression is at the end:

let x = 1;
println!("x + 1 is {}", x + 1);

Variables and expressions can be mixed in a call to println.

match

The match expression (note to self: how does an expression differ from a macro or function?) allows you to perform a different assignment to one variable based on the value of another variable (it looks a bit more complicated than that, this is a massive simplification).

An except call can be replaced with a match expression if we want to handle the Result type more flexibly (e.g. different actions depending on the value of the Err enum).

Loops

An infinite loop (the equivalent of while (1) {} in C or for {} in Go) can be created by enclosing the code with loop {}.

As with C and Go, the break statement will terminate a loop immediately.