Intro to Rust

These are notes for a quick introduction to Rust.

Overview

  • Statically typed & Compiled language.
  • Great developer experience:
    • cargo build system
    • rust-analyzer LSP

Rust features

Basics

C++Rust
std::size_tusize
std::pointerdiff_tisize
inti32
unsigned intu32
long longi64
unsigned long longu64
stringString
string_view&str
byteu8
charchar
vector<T>Vec<T>
array<int, 4>[u32; 4]
int[]&[u32]
TT
const T&&T
T&&mut T
T*unsafe { T* }
unique_ptr<T>Box<T>
optional<T>Option<T>
variant<T, E>Result<T, E>
C++Rust
for(int i = 0; i < n; ++i) {}for i in 0..n {}
while(true) {}loop {}
while(f()) {}while f() {}
do { } while (f());loop { if !f() { break; } }
switch x { case 1:; }match x { 1 => {} }
C++Rust
cout << “text” << endl;println!(“text”);
cout << 1+1 << endl;println!("{}", 1+1);
cout << n << endl;println!("{n}");

Basic syntax

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// trailing return type
fn square(x: u32) -> u32 {
    // return on last line can be omitted
    x*x
}

// mutable reference
fn increment(x: &mut u32) {
    *x += 1;
}

fn main(){
    // Introduce variables with let.
    // Types are automatically inferred.
    let a = 1;
    // b is mutable.
    let mut b = 1;
    b += 1;
    // c, d, and e are usize:
    let c: usize = 1;
    let d = 1usize;
    let e = usize::MAX;

    // No parentheses needed.
    if a > b {
        // This shouldn't happen.
        panic!();
    }

    // 0..n is a `Range`.
    // Ranges are `IntoIterator` and converted into an iterator, which is looped over.
    for i in 0..n {
        // Print i to a line on stderr.
        eprintln!("{i}");
    }

    while b < 1000 {
        increment(b);
    }

    loop {
        b = square(b);
        if b > 1000000000 {
            break;
        }
    }

    // Pattern matching
    match 5 {
        0 => panic!(),
        1 => todo!(),
        2..3 => eprintln!("small"),
        x if x%2==0 => eprintln!("even {x}"),
        _ => eprintln!("odd");
    }
}

Expressions everywhere!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
let a = { 1 + 1 };
let b = if a > 10 { a } else { 10 };
let c = loop {
    break 3;
};
let d = {
    let mut x = 1;
    while x < 1000 {
        x *= 2;
    }
    x
};
let a = match Some(5) {
    None => 0,
    Some(x) => 2*x,
};

Closures

1
2
3
4
let double = |x| 2*x;
let a = double(1);
let multiply = |x: usize, y: usize| -> usize { x * y };
let b = multiply(2, 3);

Pattern matching

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
let a: Option<i32> = Some(1);
match a {
    Some(0) => eprintln!("I am 0"),
    Some(x) if x % 2 == 0 => eprintln!("I am even"),
    Some(x) => eprintln!("I am {x}"),
    None => eprintln!("I am none"),
}

if let Some(x) = a {
    eprintln!("a = Some({x})");
}

let Some(x) = a else {
    return;
};
eprintln!("{x}");

let x = a.unwrap();

References

Ownership

Containers

1
2
3
4
5
// Create an array
let a: [usize; 10] = [1; 10];
// Create a vec
let v: Vec<usize> = vec![1usize; 10];
assert_eq!(&a, &v, "Slices are not equal!");

Traits

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
trait MyTrait {
    fn my_fn(&self);
}

impl MyTrait for usize {
    fn my_fn(&self) {
        eprintln!("I am a usize!");
    }
}

impl MyTrait for i32 {
    fn my_fn(&self) {
        eprintln!("I am a i32!");
    }
}

fn f(t: impl MyTrait) {
    t.my_fn();
}

fn main() {
    let a = 1; // i32 by default
    a.my_fn();
    let b = 1usize;
    b.my_fn();
    f(a);
    f(b);
}

Iterators

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
for i in 0..10 {
    eprintln!("i={}", i);
}

let v = (0..10).collect_vec();

for x in &v {
    eprintln!("x={}", x);
}

for (i, x) in v.iter().enumerate() {
    eprintln!("{i:>2} => {x}");
}

for x in v.iter().filter(|x| **x % 2 == 0) {
    eprintln!("x={}", x);
}

for x in v.iter().map(|x| x * x) {
    eprintln!("square: {}", x);
}

Common libraries

See blessed.rs for a list of commonly used and recommended libraries.

  • rand: random number generation.
  • clap: Command Line Argument Parsing.
  • serde: SERialization and Deserialization to json, yaml, and many other formats.
  • itertools: Extra utilities for iterating over stuff.
  • coloured: coloured terminal output.

Ecosystem

  • Release cycle
  • Unstable rust
  • cargo {build,run} -r for release mode is much faster.
  • cargo add <crate> to add a dependency from CLI.
  • Many tools, like cargo flamegraph for profiling

There is a lot of high quality documentation:

First, rust-lang.org/learn contains a lot of useful links, some of which I replicate here:

The Rust book, doc.rust-lang.org/book
A gentle step by step introduction to the Rust language and ecosystem.

Check out the page on Control Flow and find something that you’ve not seen in other languages.

The reference, doc.rust-lang.org/reference
A more formal documentation of language features. Probably not so readable for beginners.

Find the page on Traits.

Documentation, doc.rust-lang.org/std
The standard library docs. Always keep this close by, and consider making a hotkey for searching it ;)

Read some of the docs for fn and println!.

Crate registry, crates.io
Where all public crates (packages) are. Useful for searching dependencies.

Try searching cli, and make sure to sort by All-Time Downloads. Find the github page and documentation of the first result.

Crate documentation, docs.rs
Documentation for all crates!

Search for serde and go to its docs. Find documentation for the Serialize trait. Is an array of length 64 serializable? Also you can find the corresponding crates.io page.

Comprehensive Rust, https://google.github.io/comprehensive-rust/
Introduction by Google

Hands-on

Installation

Go to https://rustup.rs and follow instructions.

  • Arch Linux alternatively has the rustup package.

Also install Rust analyzer binary, the LSP.

  • rust-analyzer vscode extension
  • rust-analyzer package in your package manager.
  • via rustup: rustup component add rust-analyzer

Make sure to enable the LSP in your IDE.

Recommended: install GitHub copilot as well.

Create a project

Go to your projects folder, and run cargo new hello_world. This creates a new project:

1
2
3
4
5
6
7
8
9
> tree -a hello_world
hello_world
├── Cargo.lock
├── Cargo.toml
├── .git
│   └── ...
├── .gitignore
└── src
    └── main.rs

Hello, world!

Currently, main.rs looks like this:

1
2
3
fn main() {
    println!("Hello, world!");
}
  • fn is the syntax to introduce a new function.
  • fn main creates the main function, the entry point of a binary.
  • main() takes no arguments.
  • println! is a macro (i.e. not a regular function call) that prints its argument to standard output.

To run the program, simply do cargo run from anywhere in the projects directory.

  • This will first build the code (see /target/), if not already done.
  • It will then run the binary.

Note that cargo is the package manager and build system (and more). cargo invokes rustc, the underlying compiler.

Small project ideas

  • Compute all minimizers of a string.
  • Solve some Project Euler problems
  • Write a guessing game: the program chooses a random number and the user has to guess it with lower/correct/higher answers.