Udemy Rust Course

Set Up

Install rust (check installation by rustc --version) Install c++ build tools to avoid link.exe not found error

Intro

Safety, Concurrency, Speed Cargo - package manager, build system, docs generator Configs files use toml format Command for creating new project (called hello in this case)

cargo new hello

Variables

const c = 3;
let immutable = 5;
let mut mutable = 7;

Explain error types

rustc --explain E0384

Functions

declared with fn tail expression - without ; it becomes the return value

Modules

src/main.rs main file, while src/lib.rs is the main lib file pub makes fn public use statement shortens namespace usage standard library e.g. use std::collections::HashMap crates.io - rust community registry packages (or crates) can be added to the dependencies name = "version" format

Literal Types

u8 to u128 and i8 to i128, usize and isize, f32 and f64 byte usually means u8 underscore are ignored on number representations. e.g. 0xff_32_00_af suffix literal instead of annotating type. E.g. 5u16, 3.14f32 (or 3.14_f32) bool: true, false char - 4 bytes strings are UTF8 internally, sometimes when dealing with a single character, 1 character string

Compound Types

tuples (maximum arity of 12) let t = (1, 3.3, "asd"); let f = t.0; let g = t.1; let h = t.2; let (f,g,h) = t; // access all at once array (maximum size of 32) let buf = [1, 2, 3]; let buf = [0; 3]; // [0, 0, 0] annotation let buf : [u8; 3] = [0, 0, 0]; // even when you specify all > than 32, use vector instead

Control flow

If statement - pretty similar, no parenthesis. No auto type conversion, must always eval to bool. if statement can return a value e.g. let msg = if num==5 { "five" } else if num == 3 { "three" } else { "other" }; note semicolon usage indicating return (regular return won't work for this purpose) loops can have identifiers (tick) and can be used to break out of it e.g. 'asd : loop { // unconditional loop loop { loop { break 'asd; // also continue } } }

Strings

6 types of string in Rust 2 most used - borrowed string slice (str or &str) string literals are always borrowed string can't be modified - String (created using to_string();) or String::from("literal"); has capacity which can differ from string actual size can be modified access string by index -> complicated due to how unicode works one grapheme can be made out of multiple scalars (e.g. diacritic symbols) using word.bytes can get an addressable array of bytes (works fine for simple ascii text) package unicode-segmentations to handle graphemes nth method to index strings instead of using direct index

Ownership

Only one variable can own a value. It can be borrowed or copied. Variable goes out of scope -> Value gets trashed. Copy using .clone() method. "copy" means only stack data is copied. Clone copy heap and offset ptr to match new variable (deep copy). Passing ownership to functions and returning value means the function intends to modify the passed in value. do_something(&ref_value) It's possible to make a mutable reference to access mutable values &mut variable_or_type dereferencing: x: &mut i32; &x // mutable i32 Not possible to have multiple mutable references -> Thread safety

Struct

No classes, no inheritance. Only structs.

Traits

Similar to interfaces Traits can implement behaviours for existing types e.g. Copy trait -> object gets copied instead of moved. Good for small objects that fit in the stack. More like "components"

Collections

Most common: Vector let mut v: Vec<i32> = Vec::new() macro for creating vector: let mut v: vec![2,4,6] Also: HashMap, VecDeque, BTreeMap, LinkedList, BinaryHeap, BTreeSet (BT are binary-tree sorted versions)

Enums

Similar to Union in C++ Can store one type of value, and also it's value example enum Option<T>{Some(T), None} if let - checks for one pattern match - Checks multiple patterns enum Result<T,E>{OK(T), Err(E)} Match can use guards for checking for specific values

Closures

syntax is let sum = | x, y | { x + y } then you call it with sum(2, 3) Closures can own references and make sure variables live as long as the closure itself survives. This means you can send it to different threads etc with safety.

Thread

use std::thread; and spawn it as let handle = thread::spawn(move || { // do stuff in here }); and wait it to finish with handle.join().unwrap(); good for parallel stuff, not so good if sharing same cpu (context switching overhead) for just awaiting things like IO, use async/await instead using crossbeam::channel for testing purposes need to drop transmitter to end child threads waiting the receiver.

Final Project - Invaders

Using many libraries from https://github.com/CleanCut/rusty_engine