Rust

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 operating 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).

Cargo

Cargo is the build system and package manager for Rust (there is no obvious equivalence for C or Go, as Go has its own built-in package management and there is no standard build system or package manager for C).

A new Cargo project can be created with: cargo new project_name. This will create a new directory in the current directory with the name project_name, containing:

Cargo assumes (requires?) that your source files (i.e. .rs) are in src and other files such as documentation are in the top level directory.

cargo build will build a binary with debugging information and no optimisation, which is what you want when developing. The binary will be placed in ./target/debug/ and will have the same name as the package. It will also create or update the Cargo.lock file, which you should never edit manually.

Although you can run the binaries using their relative file paths (e.g. ./target/debug/project_name), you can do this more easily with cargo run (this will also build the file if it does not exist or is out of date). This command is the same on all platforms supported by Rust, whereas the executable filename may differ.

cargo check will make sure the code can be compiled, but will not create an executable. This can be useful if you are still working on the code, but want to ensure that there are no compile-time errors. This is a useful command to run in Git hooks, where you don't want broken code to be checked into the repository but you don't need the executable. However, on small projects and fast machines, there may not be much time difference between cargo build and cargo check.

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, which is confusing (there are good reasons for the immutability, but calling something a variable when it is immutable is not a good choice). 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, which may seem even more surprising and counter-intuitive. Again, sensible choice from a programming perspective but a poor choice of name.

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).

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.