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:
- Cargo is the Rust workflow tool - use it for everything.
- There's always an implicit dependency on the Rust compiler and the standard library - though I think this is common across most build systems (it certainly is the case in PHP, C and Go).
- The biggest strength of Rust is memory safety, which is a change from C where memory-related issues (invalid pointers, forgetting to free memory, re-use after free etc.) are the cause of a lot of bugs.
- Rust is strongly typed but also has inferred types - similar to Go (both) but different to C (strong but not inferred in declarations) and PHP (weak by default, but inferred).
- Result and Option types can be very useful - no runtime exceptions, no NULL pointers.
- Zero cost abstractions - iterators for example compile to the same code as a for loop.
- Rust has narrower architecture support than C, but it works on all the ones you're likely to use, unless you're doing embedded or esoteric work.
- Rust isn't as well-suited to rapid prototyping and iteration as some other languages.
- Editions let you specify which features are supported, e.g. async in 2018.
- Each Rust compiler knows about all previous editions, so it can compile code for them. You can also mix and match crates with different editions in most cases.
- There's a cultural bias against very small libraries, e.g. leftpad would be discouraged. This is partly due to the crates.io namespace being flat. blessed.rs is a website for finding the 'recommended' crates for specific tasks.
- Rust is quite hard to read initially - feels more verbose than C and Go in places (C in particular has the advantage of having a very small core).
- ? is important and used a lot in idiomatic Rust. I struggled to get my head around this, but it feels like something you get used to over time - a bit like * and how you (de)reference pointers in C.
- All 'variables' are immutable by default.
- Any function that doesn't take self as a parameter and returns Self is a constructor.
- Variables can be copied or cloned - these are subtly different.
- The borrow checker is the thing that confuses new programmers more than anything else. If you don't care about inefficiency, you can use clone to get around its restrictions.
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:
.gitignore: Ignore files that should not be committed to version control, such as binaries (i.e./target). A new Git repository will be initialised, unless the project is within an existing repository. You can prevent this by adding--vcs noneto the arguments tocargo new.Cargo.toml: Build configuration and dependencies, in INI format.src/main.rs: Skeleton source file, containing the Hello World code.
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.