learning

Rust is a general-purpose programming language emphasizing performance, type safety, and concurrency. It enforces memory safety, meaning that all references point to valid memory. It does so without a traditional garbage collector; instead, both memory safety errors and data races are prevented by the “borrow checker”, which tracks the object lifetime of references at compile time.

reasons

  • I want to add a systems programming language to my go-to languages
  • compile time type safety
  • compile to any target, including windows
  • interesting concurrency model
  • use in cyber security
    • secure and performant security tooling
    • secure and performant malware

rust cybersec quotes

![[Rust  The Secrets of Rising Star in Cybersecurity - CloudThat Resources]]

rust project ideas

  • customising Nostr relay nostr-rs-relay
  • boflab rust version
  • rewriting rasplogger
  • turning tkconv into grafana

rust codebases

  • rustscanner (nmap)
  • ferox (file enumeration)
  • libafl (fuzzer)
  • lsniffglue (sniffer)

courses

libraries to investigate

  • Serde is a powerful serialization and deserialization framework for Rust.
  • Tokio is an asynchronous runtime for Rust, providing an event-driven platform for building fast, reliable, and lightweight network applications
  • Rowan for parsing. 
  • web servers
    • Rocket is a web framework for Rust that makes it simple to write fast, secure web applications
    • Axum for building web servers on top of Hyper (which is now 1.0!). 
  • Anyhow is used for flexible error handling in Rust.
  • Jiff for dates. 
  • Diesel is a safe, extensible ORM (Object-Relational Mapper) and Query Builder for Rust.
  • Tantivy as an alternative to Lucene.

rust topics

types

  • only u8 ints be converted to chars
  • .len returns number of bytes, not the amount of characters
  • . chars().count() returns amount of chars in string
  • add _ to numbers for readability, they are ignored e.g 1_000_000

functions

  • skinny arrow indicates return type
  • line without ; indicates value that gets returned
  • last line of function is the return

blocks

  • capture variables in strings in curlies “var: {var}“
  • variable lifetime ends outside of block

logging

  • pretty print datastructures other than strings with {:?}
  • multiline pretty print with {:#?}

traits

  • ”what a type can do”

variables

  • drill down into class const’s with :: , e.g. u8::MAX
  • make variables mutable with mut
  • you can’t change types of mut variable
  • when shadowing variables in blocks you can recover the original value after the block scope is closed
  • variables can be declared uninitialised, and then used within scopes without dying.

memory

  • memory on stack needs fixed sizes
  • memory on heap can have unknown data length, but it’s slower
  • sometimes heap is not even available (embedded devices)

references

  • rust reference is a “memory safe” pointer: a reference to “owned” memory
  • references point to memory of other value: “borrowing” values
  • references are prefixed with &
    • let my_reference = &my_value
  • mem::size_of::<u8> shows size of type
  • mem::size_of_val(&reference) shows size of value
  • functions cannot return references to values that “die” in the function scope
    • dereferences a reference and asked you to operate on the value

ownership

  • you can pass objects that you own around
  • you can pass a references to data
    • then function can view the data but not mutate it
  • you can have unlimited unmutable references to values (read only)
  • you can have only one mutable reference to a value (write)

moving / borrowing

  • when passing an owned object to a function (var: String), the function takes ownership and value will die at the end of the scope. This is called a “move” as the data is moved to a different scope
  • when passing a var: &String, the value doesn’t move or change ownership, value can be viewed not mutated and will not die
  • when passing a var: &mut String mutable reference, value doesn’t change ownership, doesn’t die and is mutable.

strings

  • defining a regular fixed length string results in an &str
    • is a reference with known length to memory ref-stir or string slice
  • a String object has data on the heap, owns it’s data and has functionality to mutate the data
  • format! Macro is like println but creates a String object

copy / clone

  • copy types (ints floats bool) always get copied when passed to a Function
  • strings don’t get copied but are clone-able

printing

  • multiline strings are allowed
  • use raw to print loads of escaped characters println!(r#"string with bunch " of " escapable chars"#)
  • use b to print string as bytes
  • use to print hex
  • use to print pointer adress, as in the memory adress the pointer points to
  • \u{hex} to print Unicode character
  • printing can also specify padding alignment and padding character

arrays

  • typing arrays type first then length [&String; 2]
  • prefill array with let mut buffer = [0; 640]
  • slicing array with position, count. Exclusive: &array[0..2] inclusive &array[0..=2]

vectors

  • kind of like String for &str is Vector for &array
  • Vec<String> vector of strings
  • Vec<(i32,i32)> vector of tuple of 2 i32s
  • all items within vector have the same type
  • vector.capacity() shows capacity in vector before value is copied and doubled, usually 4 when there is 1 value.
  • Vec::with_capacity(8) when you know the future vector size avoids reallocation, which is better
  • vector: Vec<_> = [1,2,3].into()

tuples

  • tuples can contain multiple different types
  • access tuple members with dot: tuple.0
  • destructure vectors with tuple definition
    • let vector = vec!["one", "two", "three"]
    • let (a, _, c) = (vector[0],vector[1],vector[2])

match

  • match is like a switch but needs to be exhaustive
  • code block, fat arrows and “arms” for each case
fn main() {
    let my_number: u8 = 5;
    match my_number {
        0 => println!("it's zero"),
        1 => println!("it's one"),
        2 => println!("it's two"),
        _ => println!("It's some other number"),
    }
}
  • ”match guards” are if statements within a match
  • matches also work with tuples, allowing you to check multiple things and match them to a specific case, and catching all other cases
  • match stops when it finds a hit
  • match always returns the same type
  • use @ to create a variable for the match value to use it in a print statement
    • number @ 13 => println!("{} is unlucky

structs

  • unit struct, has nothing: struct FileDirectory
  • unnamed Tuple struct, only has types: struct Color(u8,u8,u8)
  • named struct, has tuple with values: struct Light { on: bool, brightness: u8, color: Color}
  • structuring allows you to not have to assign the values if they are named the same:
let on = true;
let brightness = 50;
let color = (50,50,50);
let light = Light {
on, brightness, color,
}

enum

  • use struct if you have many different things, use enum if you have one thing with many options
  • enum States { Play, Pause, Stop }
  • enums combine perfectly with match to name the states:
match state {
        ThingsInTheSky::Sun => println!("I can see the sun!"),
        ThingsInTheSky::Stars => println!("I can see the stars!")
    }
  • enums can contain data when created:
enum ThingsInTheSky {
    Sun(String), // Now each variant has a string
    Stars(String),
}
  • import enums to allow us to use the members directly:
fn match_mood(mood: &Mood) -> i32 {
    use Mood::*; // We imported everything in Mood. Now we can just write Happy, Sleepy, etc.
    let happiness_level = match mood {
        Happy => 10, // We don't have to write Mood:: anymore
        Sleepy => 6,
        NotBad => 7,
        Angry => 2,
    };
    happiness_level
}
  • enums members have indexes, get them by casting the value to an i32 if you change them the numbering goes on
  • if you want to create a vector with different types, you can use enum with multiple values

loops

  • loop {} loops indefinitely until break; is called
  • you can name loops with 'loop_outer: loop { break 'loop_outer; }
  • for number in [0..2] { } to loop, use _ if you don’t need the variable
  • break can return a value that can be assigned from the loop

implementing structs and enums

  • methods: take “self” and mutate on object
  • associated functions: don’t take self, are related
enum Mood {
    Good,
    Bad,
    Sleepy,
}

impl Mood {
    fn check(&self) {
        match self {
            Mood::Good => println!("Feeling good!"),
            Mood::Bad => println!("Eh, not feeling so good"),
            Mood::Sleepy => println!("Need sleep NOW"),
        }
    }
}

fn main() {
    let my_mood = Mood::Sleepy;
    my_mood.check();
}

attributes

  • added to structs and enums #[derive(Debug)]

dereferencing

  • when using the dot operator . you don’t have to worry about dereferencing a reference:
    • let reference_item = &item
    • let double_reference_item = &reference_item
    • double_reference_item.compary_number(8) works

generics

  • Used if there are multiple types able to be passed to a function
    • fn print_value<T>(value: T) { println!("{}", value)}
  • Generic is usually called T
  • specify which trait, for instance debug, is implemented on a generic:
    • fn print_value <T: Debug>(value: T)
  • if you want to compare generics, you need PartialOrd

option

  • use option as return type when you deal with values that might exist, or might not exist
  • use None and Some in code where you need to return an option
fn take_fifth(value: Vec<i32>) -> Option<i32> {
	if (..) {
		None
	} else {
		Some(value[4])
	}
}
  • the None or Some values can be “unwrapped”. You only want to unwrap if you are sure there is a value inside
  • we can use a match on an option to safely deal with the None value
  • we can use is_some() on an option to see if it contains a Some value

result and error handling

  • result is similar to option, but about ok or error instead of no value or value
  • implementing error handling:
fn give_result(input: i32) -> Result<(), ()> {
	if input % 2 == 0) {
		return Ok(())	
	} else {
		return Err("input couldnt be processed")
	}
}

fn main() {
	if give_result(5).is_ok() {
		println!("it was ok");	
	} else
		println!("it was an error");	
	}
}

errors

  • you can define your own errors, similar to this standard library function
pub fn from_utf8(vec: Vec<u8>) -> Result<String, FromUtf8Error>
  • if you only want to do something when the value is not None:
let my_vec = vec![2, 3, 4];
for index in 0..10 {
	if let Some(number) = my_vec.get(index) {
		println!("the number is {}", number)
	}
}
  • you can do something similar with while let, e.g. popping a vector until it returns none while let Some(information) = city.pop()

hashmap

  • hashmaps are not ordered
  • create with HashMap::new()
  • populate with hashmap.insert(key, value)
  • hashmap.get() returns an Option
  • inserting with existing key will overwrite value
  • the .entry() method returns a mutable reference to the entry if the key exists, allowing you to change its value
  • if we chain .entry(key).or_insert(0) the key with value 0 will be inserted into the hashmap if it didn’t exist yet
  • we can then use that reference to for instance increase a counter *reference += 1;
  • you can also use or_insert to create vectors with items as values

btreemap

  • btreemaps exactly like hashmaps but are sorted by key
  • iterate with for (key, value)