Damien Gonot
Home Blog Notes About

Rust

Homepage / Notes / Computer Science / Programming Languages / Rust

Basics

Anatomy

fn main() {

}

main function is special: always the first code that runs in every executable Rust program. Function parameters go inside the parentheses: () And code for the function goes inside curly brackets: {}

Format

rustfmt can be used to automatically format our Rust code. Indent 4 spaces, no tabs.

Comments

fn main() {
    // This is a comment
}

Hello, World!

fn main() {
    println!("Hello, World!");
}
Hello, World!

println!() is a Rust macro. ! means it's a macro and not a function. ; at the end of the line indicates that the expression is over.

Operators

https://doc.rust-lang.org/book/appendix-02-operators.html

Cargo

Resources

Cargo book https://doc.rust-lang.org/stable/cargo/

Language Features

Primitives

Scalar Types

Compound Types

Variables

Mutable Variables

fn main() {
    let x = 5 + 5;
    x += 5;
    println!("{}", x);
}
error: Could not compile `cargoSivb4X`.

cannot assign twice to immutable variable

fn main() {
    let mut x = 5 + 5;
    x += 5;
    println!("{}", x);
}
15

Destructuring Assignment

Starting from Rust 1.59:

fn main() {
    let (a, b) = (1, 2);
    println!("{a}");
    println!("{b}");
}
1
2

Functions

snakecase is the convention for naming functions

fn main() {
    println!("{}", "Hello");
    another_function();
}

fn another_function() {
    println!("{}", "World!");
}
Hello
World!

Function Arguments

Arguments' types have to be specified:

fn greet(name: &str) {
    println!("Hello, {name}");
}

greet("Damien");
Hello, Damien

Function Return

Function's return type have to be specified too:

fn sum(x: i32, y: i32) -> i32 {
    x + y
}

println!("{}", sum(4, 5));
9

Function's automatically return the value of the last expression, but can be returned early using the return keyword:

fn sum(x: i32, y: i32) -> i32 {
    return x + y;
}

println!("{}", sum(4, 5));
9

Strings

fn main() {
    let s = "Damien";
    println!("{}", s);
}
Damien

String to chars

fn main() {
    let s = "Damien".chars();
    println!("{:?}", s);
}
Chars(['D', 'a', 'm', 'i', 'e', 'n'])

Numbers

let x = 9;
println!("{x}");
9

Ranges

let range = 1..5;

for i in range {
    println!("{i}");
}
1
2
3
4

Right-inclusive range:

let range = 1..=5;

for i in range {
    println!("{i}");
}
1
2
3
4
5

Formatted Print

fn main() {
    let a = 1 + 1;
    println!("{}", a);
}
2

Starting from Rust 1.58:

fn main() {
    let a = 1 + 1;
    println!("{a}");
}
2

https://www.rustnote.com/blog/format_strings.html

Data Structures

Sequence Collections

Tuple

Tuples can contain multiple types. Tuples have a fixed length.

let tup: (i32, f64, u8) = (500, 6.4, 1);
Array

Every element of an array has to have the same type. Array in Rust have a fixed length.

let a = [1, 2, 3, 4, 5];

Arrays are useful when you want your data to be allocated on the stack rather than the heap.

To write an array's type, you have to specify the type AND the number of elements in the array, separated by a colon, and enclosed in square brackets:

let a: [i32; 5] = [1, 2, 3, 4, 5];

It's possible to initialize an array that contains the same value for each element by specifying the initial value, followed by a semicolon, and then the length of the array, enclosed in square brackets:

let a = [3; 5];

println!("{:?}", a);
[3, 3, 3, 3, 3]
Accessing array elements

By using the index:

let a = [1, 2, 3, 4, 5];

let first = a[0];
let second = a[1];

println!("{:?}", first);
println!("{:?}", second);
1
2
Vec

A type has to be specified when creating an empty vec as type can't be inferred:

let v: Vec<i32> = Vec::new();

println!("{:?}", v);
[]

Not necessary when initializing the vec with values:

let v = vec![1, 2, 3];

println!("{:?}", v);
[1, 2, 3]
Accessing values
let mut v = vec![1, 2, 3];

println!("{}", &v[0]);
println!("{}", &v[2]);
1
3
Adding values

When adding values later in the code, Rust can also infer the vec type

let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);

println!("{:?}", v);
[5, 6, 7]
Removing values
let mut v = vec![1, 2, 3];
v.remove(0);

println!("{:?}", v);
[2, 3]
Iterating over values
let v = vec![9, 10, 11];
for i in &v {
    println!("{}", i);
}
9
10
11

Values can be mutated while iterating, but they have to be de-referenced by using *

let mut v = vec![9, 10, 11];
for i in &mut v {
    *i += 1;
}

println!("{:?}", v);
[10, 11, 12]
VecDeque

A double-ended queue implemented with a growable ring buffer.

use std::collections::VecDeque;

let deq: VecDeque<u32> = VecDeque::new();
println!("{:?}", deq);
[]

Initializing with values:

use std::collections::VecDeque;

let deq = VecDeque::from([-1, 0, 1]);
println!("{:?}", deq);
[-1, 0, 1]
Push / Pop
use std::collections::VecDeque;

let mut deq: VecDeque<u32> = VecDeque::new();
deq.push_front(1);
deq.push_front(2);
println!("{:?}", deq);
[2, 1]
use std::collections::VecDeque;

let mut deq: VecDeque<u32> = VecDeque::new();
deq.push_back(1);
deq.push_back(2);
println!("{:?}", deq);
[1, 2]
use std::collections::VecDeque;

let mut deq = VecDeque::from([0]);
deq.push_front(1);
deq.push_front(2);
deq.push_back(3);
deq.push_back(4);
deq.push_back(5);
println!("{:?}", deq);
[2, 1, 0, 3, 4, 5]
use std::collections::VecDeque;

let mut deq = VecDeque::from([1, 2, 3]);
deq.pop_front();
println!("{:?}", deq);
[2, 3]
use std::collections::VecDeque;

let mut deq = VecDeque::from([1, 2, 3]);
deq.pop_back();
println!("{:?}", deq);
[1, 2]
LinkedList

A doubly-linked list with owned nodes.

Map Collections

HashMap

Allows to store key/value pairs.

use std::collections::HashMap;

let mut map = HashMap::new();
println!("{:?}", map);

map.insert("Japan", "Tokyo");
map.insert("France", "Paris");
map.insert("Canada", "Ottawa");
println!("{:?}", map);

map.remove("France");
println!("{:?}", map);
{}
{"France": "Paris", "Japan": "Tokyo", "Canada": "Ottawa"}
{"Japan": "Tokyo", "Canada": "Ottawa"}
BTreeMap

Equivalent to HashMaps but "sorted".

use std::collections::BTreeMap;

let mut btree = BTreeMap::new();
println!("{:?}", btree);

btree.insert("Germany", "Berlin");
btree.insert("United Kingdom", "London");
btree.insert("Taiwan", "Taipei");
println!("{:?}", btree);

btree.remove("United Kingdom");
println!("{:?}", btree);
{}
{"Germany": "Berlin", "Taiwan": "Taipei", "United Kingdom": "London"}
{"Germany": "Berlin", "Taiwan": "Taipei"}

Set Collections

HashSet

Set form of HashMap, meaning no duplicate keys are allowed.

use std::collections::HashSet;
let mut set = HashSet::new();
set.insert("key");
set.insert("key");
println!("{:?}", set);
{"key"}

Note how "key" is only present once, not twice.

BTreeSet

Set form of BTreeMap.

use std::collections::BTreeSet;
let set: BTreeSet<u32> = BTreeSet::new();

Structs

A struct contains fields. Access value by dot notation.

#[derive(Debug)]
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

let mut user1 = User {
    email: String::from("[email protected]"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};

println!("{:?}", user1);
println!("{:?}", user1.email);
user1.email = String::from("[email protected]");
println!("{:?}", user1.email);
User { active: true, username: "someusername123", email: "[email protected]", sign_in_count: 1 }
"[email protected]"
"[email protected]"
Field Init Shorthand
#[derive(Debug)]
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

let email = String::from("[email protected]");
let username = String::from("[email protected]");

let mut user1 = User {
    email, // instead of email: email,
    username, // instead of: username: username,
    active: true,
    sign_in_count: 1,
};

println!("{:?}", user1);
User { active: true, username: "[email protected]", email: "[email protected]", sign_in_count: 1 }
Struct Update Syntax
#[derive(Debug)]
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

let user1 = User {
    email: String::from("[email protected]"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};

let user2 = User {
    email: String::from("[email protected]"),
    ..user1
};

println!("{:?}", user2);
User { active: true, username: "someusername123", email: "[email protected]", sign_in_count: 1 }
Defining Methods
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

let rect1 = Rectangle {
    width: 30,
    height: 50,
};

println!("{:}", rect1.area());
1500

Control Flow

If/else statements

fn sum(x: i32, y: i32) -> i32 {
    return x + y;
}

if sum(4, 5) > 10 {
    println!("This is impossible");
} else if sum(4, 5) == 9 {
    println!("This is correct");
} else {
    println!("This is impossible");
}
This is correct
Inline conditional
let x = if true { 1 } else { 0 };

println!("{}", x);
1

Loop

let mut counter = 0;

loop {
    if counter == 5 {
        break;
    }

    counter += 1;
    println!("(:");
}
(:
(:
(:
(:
(:

Loops can return a value by providing it to the break keyword:

let mut counter = 0;

let nth = loop {
    if counter == 5 {
        break counter;
    }

    counter += 1;
};

println!("Loop has been executed {nth} times");
Loop has been executed 5 times
Labeled loops

By default, break applies to the innermost loop. You can specify a loop label to break a specific loop.

let mut count_one = 0;
let mut count_two = 10;

'loop_one: loop {
    println!("count_one = {count_one}");

    loop {
        println!("count_two = {count_two}");
        if count_two == 0 {
            break;
        }
        if count_two == 5 {
            break 'loop_one;
        }
        count_two -= 1;
    }

    count_one += 1;
}
count_one = 0
count_two = 10
count_two = 9
count_two = 8
count_two = 7
count_two = 6
count_two = 5

While

let mut number = 0;

while number != 6 {
    println!("{number}");

    number += 1;
}
0
1
2
3
4
5

Iterators

Since Rust 1.23, no need to call .iter()

fn main() {
    for i in [1, 2, 3] {
        println!("{}", i);
    }
}
1
2
3

Iterator methods

https://doc.rust-lang.org/std/iter/trait.Iterator.html

Reverse
for i in (0..4).rev() {
    println!("{i}");
}
3
2
1
0
Min/Max
println!("{}", [1, 2, 3].iter().min().unwrap());
1
println!("{}", [1, 2, 3].iter().max().unwrap());
3
Last
println!("{}", [1, 3, 5].last().unwrap());
5
Map
println!("{:?}", [1, 3, 5].map(|x| 2 * x));
[2, 6, 10]
Filter
(1..20).filter(|x| x % 3 == 0).for_each(|i| println!("{}", i));
3
6
9
12
15
18
Fold / Reduce
let a = [1, 2, 3];
let sum = a.iter().fold(0, |acc, x| acc + x);
println!("{}", sum);
6

Misc

Naming conventions: https://doc.rust-lang.org/1.0.0/style/style/naming/README.html

No garbage collection: have to manage memory yourself

Pattern matching through match :)

Packages

Yew

https://yew.rs/docs/intro/ http://www.sheshbabu.com/posts/rust-wasm-yew-single-page-application/

Yew is a modern Rust framework for creating multi-threaded front-end web apps using WebAssembly.

Sauron

https://github.com/ivanceras/sauron

Sauron is a versatile web framework and library for building client-side and/or server-side web applications with strong focus on simplicity. It is suited for developing web application which uses progressive rendering.

Iced

https://github.com/hecrj/iced

A cross-platform GUI library for Rust, inspired by Elm

Poem

https://github.com/poem-web/poem

A full-featured and easy-to-use web framework with the Rust programming language.

create-rust-app

https://github.com/Wulf/create-rust-app

Set up a modern rust+react web app by running one command.

Leptos

https://github.com/leptos-rs/leptos

Leptos is a full-stack, isomorphic Rust web framework leveraging fine-grained reactivity to build declarative user interfaces.

Sycamore

https://sycamore-rs.netlify.app/

A reactive library for creating web apps in Rust and WebAssembly

Resources

Main

Other

Book

Zero to Production in Rust

https://www.zero2prod.com/

Black Hat Rust

https://kerkour.com/black-hat-rust