Notes on Rust
These are the notes i'm taking on my journey to learn Rust.
I use `TCM Security Rust 101 course`, and ZeroPointSecurity `Rust for n00bs` course.
Enjoy the notes and drop me any feedback ! ;)
Table of contents:
- Class notes
- integers
- type of variables
- scope and shadowing
- suffixes and underscores
- tuples
- arrays
- slices
- strings
- escaping
- math operators
- dependencies
- comparison operators
- conditionnal statements
- match
- loops
- functions
- Collections
- structures
- impl & associated functions
- enums / or enumeration
- traits
- ownership / moving ownership
- references & borrowing
- input/output
- error handling - Result, Ok, Err, ?, unwrap() and the panic! macro
- Generics & bounds
- Closures
- Concurrency
- Challenges
- Projects
- Some concepts:
- Questions:
- Acknowledgments
Class notes
integers
Unsigned int and signed int from 8 to 128. No unsigned floating point types in Rust. f32 and f64 no more.
fn main() {
println!("Hello, world!"); // ! after println denotes that we're calling a macro, rather than another function.
// Macros in Rust are a form of metaprogramming
println!("max size of a u32: {}", u32::MAX);
println!("max size of a u128: {}", u128::MAX);
println!("min size of a i128: {}", i128::MIN);
println!("max size of a f32: {}", f32::MAX);
}
/*
Hello, world!
max size of a u32: 4294967295
max size of a u128: 340282366920938463463374607431768211455
min size of a i128: -170141183460469231731687303715884105728
max size of a f32: 340282350000000000000000000000000000000 */
type of variables
const NUMBER: i32 = 18;scopescope
fn main() {
let mut hello: &str = "hello world!";
println!("{}", hello);
hello = "hello again";
println!("{}", hello);
let x = 1;
let y: i32 = 2;
println!("x + y = {}", x+y);
println!("number: {}", NUMBER);
}
/*
hello world!
hello again
x + y = 3
number: 18 */
scope and shadowing
Created with {}
fn main() {
// outter scope
let x = 1;
let y: i32 = 3;
{
// inner scope
let y: i32 = 2;
println!("inner scope x + y = {}", x+y);
}
// outer scope
println!("outer scope x + y = {}", x+y);
}
/*
x + y = 3
x + y = 4 */
suffixes and underscores
fn main() {
let x: f32 = 42_000f32;
let y: i32 = 1_000_000;
println!("x = {}", x);
println!("y = {}", y);
}
/*
x = 42000
y = 1000000 */
tuples
() - compound type, collection - multiple values of different data type - fixed size
fn main() {
let student_a = ("toto", "A", 3.8);
/*
let student_name = student_a.0;
let student_grade = student_a.1;
let student_gpa = student_a.2;
*/
let (student_name, student_grade, student_gpa) = student_a; // replaces the above
println!("my name is {} my grade is {} my gpa is {}", student_name, student_grade, student_gpa);
let tuple: (&str, &str, i32) = ("Charles", "Dickens", 1812);
let (first_name: &str, last_name: &str, dob: i32) = tuple;
println!("{} {} was born in {}.", first_name, last_name, dob);
}
/*
my name is toto my grade is A my gpa is 3.8
Charles Dickens was born in 1812 */
arrays
[] - compound type, collection - multiple values of a single data type - fixed size
fn main() {
let students = ["heath", "bob", "linda"];
println!("first student is {}", students[0]);
let array: [i32;5] = [1,2,3,4,5];
println!("{:?}", array);
let arr: [i32; 1000] = [0; 1000];
println!("{:?}", arr);
}
/*
first student is heath
[1, 2, 3, 4, 5]
3
[0, 0, 0, 0, 0, 0, ......... 0] */
slices
References to a contiguous sequence of elements within a collection (array, string) - can use a portion of data without owning it - debug format {:?}
fn main() {
let mut arr = [1,2,3,4,5];
let slice = &mut arr[1..3];
println!("{:?}", slice);
/*
let slice = &arr[4..5];
println!("{:?}", slice);
*/
slice[0] = 6;
slice[1] = 7;
println!("{:?}", arr);
}
/*
[2, 3]
[1, 6, 7, 4, 5] */
strings
Strings: String, &str
String: heap-allocated - owned - mutable. Converted to string slice with &
or as_str()
. Can use push_str()
on mut
.
&str: string slice - borrowed - immutable - often used to pass string data between functions or to extract substrings from a larger string. Converted to a String using .to_string()
or String::from()
Concatenate with concat()
or format!()
or append a slice to a str with +
.
fn main() {
let mut hello: &str = "hello world!";
let mut name = "toto";
name.push_str(" test");
println!("{}", name);
}
// ERROR push_str doesn't exist
//-----------------------------------------------------------------------------
fn main() {
let mut name = String::new(); // creates an empty, mutable String
name.push_str(" test"); // push_str() appends a string slice (&str) to the end of the String.
println!("{}", name);
}
/* test */ // WORKS bcz push_str exists
//-----------------------------------------------------------------------------
fn main() {
let mut name: String = String::new();
name.push_str("im");
name.push_str(" toto");
name.push_str(" test");
println!("{}", name);
}
/* im toto test */
//-----------------------------------------------------------------------------
fn main() {
let name = "toto".to_string(); // converts the &str into a heap-allocated String => owned and mutable
println!("{}", name);
let name: String = String::from("toto"); // create a String from a &str, allocates a new String with the same value as the input string slice.
// creates a new variable that shadows the previous name
println!("{}", name);
}
/*
toto
toto */
escaping
/*
\: backslash
": double quote
': single quote
n: newline
r: carriage return
t: tab
0: null character
xNN: hexadecimal escape sequence, where NN is a two-digit hexadecimal number that represents a Unicode scalar value
u{NNNN}: Unicode escape sequence, where NNNN is a four-digit hexadecimal number that represents a Unicode scalar value
*/
fn main() {
let message = "1hello, \"world\"!\n";
println!("{}", message);
println!("2hello,
world");
println!("3hello, \
world");
println!("{}", concat!("4hello, ", "world"));
let heart = '\u{2764}';
println!("{}", heart);
let message = r#"5hello, "world"!\n"#;
println!("{}", message);
}
/*
1hello, "world"!
2hello,
world
3hello, world
4hello, world
❤
5hello, "world"!\n */
math operators
fn main() {
let x = 10;
let y = 3;
let x_float = x as f64;
let y_float = y as f64;
println!("{} + {} = {}", x, y, x + y);
println!("{} - {} = {}", x, y, x - y);
println!("{} * {} = {}", x, y, x * y);
println!("{} / {} = {}", x, y, x / y);
println!("{} / {} = {}", x_float, y_float, x_float / y_float);
println!("{} ^{} = {}", x, y, u32::pow(x, y));
}
/*
10 + 3 = 13
10 - 3 = 7
10 * 3 = 30
10 / 3 = 3
10 / 3 = 3.3333333333333335
10 ^3 = 1000
10 ^3 = 1000 */
dependencies
Add “rand = 0.8.5” in Cargo.toml
or from CLI cargo add rand
use std::io;
use rand::Rng;
fn main() {
let x = rand::thread_rng().gen_range(1..101);
let y = 3;
let x_float = x as f64;
let y_float = y as f64;
println!("{} + {} = {}", x, y, x + y);
println!("{} - {} = {}", x, y, x - y);
println!("{} * {} = {}", x, y, x * y);
println!("{} / {} = {}", x, y, x / y);
println!("{} / {} = {}", x_float, y_float, x_float / y_float);
println!("{} ^{} = {}", x, y, u32::pow(x, y));
println!("{} ^{} = {}", x, y, i32::pow(x.try_into().unwrap(), y)); // convert a i32 into u32
}
/*
37 + 3 = 40
37 - 3 = 34
37 * 3 = 111
37 / 3 = 12
37 / 3 = 12.333333333333334
37 ^3 = 50653
37 ^3 = 50653 */
comparison operators
fn main() {
let a = 5;
let b = 10;
let c = true;
let d = false;
println!("a > b: {}", a > b); // false
println!("a >= b: {}", a >= b); // false
println!("a < b: {}", a < b); // true
println!("a <= b: {}", a <= b); // true
println!("a == b: {}", a == b); // false
println!("a != b: {}", a != b); // true
println!("True or False: {}", c || d); //true
println!("True or True: {}", c || c); //true
println!("False or False: {}", d || d); //false
println!("True and False: {}", c && d); //false
println!("True and True: {}", c && c); //true
println!("False and False: {}", d && d); //false
}
conditionnal statements
use std::io::{self, Read};
fn main() {
println!("how much money do u have?");
let mut input_money = String::new();
io::stdin().read_line(&mut input_money);
let money: i32 = input_money.trim().parse().expect("input not integer");
println!("how old r u?");
let mut input_age = String::new();
io::stdin().read_line(&mut input_age);
let age: i32 = input_age.trim().parse().expect("input not integer");
if (age >= 21) && (money >= 5) {
println!("were getting a drink!");
} else if (age >= 21) && (money < 5) {
println!("need more money");
} else if (age < 21) && (money >= 5) {
println!("nice try");
} else {
println!("not old enough, no money, no drink");
}
}
/*
how much money do u have?
3
how old r u?
2
not old enough, no money, no drink */
match
pattern matching - arm && all possible values must be covered
use std::cmp::Ordering;
fn main() {
let candidacy_age = 28;
match candidacy_age {
25 | 26 | 27 | 29 => println!("cannot run for the house"),
1..=24 => println!("cannot hold office!"),
30..=34 => println!("cant run for senate"),
35..=i32::MAX => println!("can run for president"),
_ => println!("are you an infant?")
};
let my_age = 25;
let drinking_age = 21;
match my_age.cmp(&drinking_age) {
Ordering::Less => println!("cant drink!"),
Ordering::Equal => println!("can drink"),
Ordering::Greater => println!("woow can drink!")
};
}
/*
are you an infant?
woow can drink! */
loops
use std::{i32, io::{self, Read}};
use std::cmp::Ordering;
fn main() {
// FOR
let mut veggie = ["cucumber", "spinach", "cabbage"];
for x in veggie.iter() {
println!("{}", x);
}
// 192.168.1.1 - 192.168.1.254
/* let mut ip = 1..254;
for y in ip {
println!("{}", y);
} */
// WHILE
let mut i = 1;
while i < 3 {
println!("{}", i);
i += 1;
}
// INFINITE
let mut y = 0;
println!("counting");
loop {
y += 1;
println!("{}", y);
if y == 5 {
println!("reached 5!");
continue;;
}
if y == 10 {
println!("reached 10! exiting");
break;
}
}
}
/*
cucumber
spinach
cabbage
1
2
counting
1
2
3
4
5
reached 5!
6
7
8
9
10
reached 10! exiting */
functions
use std::{i32, io::{self, Read}, os::unix::fs::PermissionsExt};
use std::cmp::Ordering;
fn who_am_i() {
let name = "toto";
let age = 32;
println!("my name is {} and my age is {}", name, age);
}
fn add_stuff(num: i32) {
println!("{}", num + 100);
}
fn add(x: i32, y: i32) {
println!("{}", x+y);
}
fn mult(x: i32, y: i32) -> i32 {
x * y
}
fn add_mult(x: i32, y: i32) -> (i32, i32) {
(x+y, x*y) // in main(): first result will be passed to add, second to mult
}
fn main() {
who_am_i();
who_am_i();
add_stuff(100);
add(7, 7);
println!("{}", mult(5, 4));
let (added, multiplied) = add_mult(4, 7);
println!("added: {}", added);
println!("multiplied: {}", multiplied);
}
/*
my name is toto and my age is 32
my name is toto and my age is 32
200
14
20
added: 11
multiplied: 28 */
Collections
vectors
sequence collection - single data type - uses generics (Vec<T>
: allows for any datatypes) variable length - slower than arrays but more flexible => resizable & dynamic. Has specific get()
function.
Useful when checking for program arguments: dont know how much there will be => use vectors
use std::env::args;
fn main() {
let mut vec1: Vec<i32> = Vec::new(); // vec1, a vector that stores i32 integers
let mut vec2: Vec<i32> = vec![1, 2, 3];
vec1.push(1); // add an element to `vec1`
vec2.push(4);
let second_element = vec2[1];
for element in vec1.iter() {
println!("vec1 element: {}", element);
}
for element in vec2.iter() {
println!("vec2 element: {}", element);
}
println!("the second vec2 element is: {}", second_element);
println!("the length of vec2 is: {}", vec2.len());
let first_arg = args[1];
let second_arg = args[2];
}
/*
vec1 element: 1
vec2 element: 1
vec2 element: 2
vec2 element: 3
vec2 element: 4
the second vec2 element is: 2
the length of vec2 is: 4 */
maps
stores data in key-value pair - HashMap & BtreeMap - Btree: sorted data - HashMap: unsorted data.
use std::collections::{HashMap, BTreeMap};
fn main() {
///////////////// HASHMAP
let mut hashmap = HashMap::new();
hashmap.insert(1, "number 1");
hashmap.insert(2, "number 2");
hashmap.insert(3, "number 3");
for kvp in hashmap.iter() {
println!("h_key: {} h_value: {}", kvp.0, kvp.1);
}
///////////////// BTREEMAP
let mut btreemap = BTreeMap::new();
btreemap.insert(1, "number 10");
btreemap.insert(2, "number 11");
btreemap.insert(3, "number 12");
println!();
for kvp in btreemap.iter() {
println!("b_key: {} b_value: {}", kvp.0, kvp.1);
}
let old_value = btreemap.get(&3);
match old_value {
Some(v) => println!("b_key 3 old value: {}", v),
None => println!("not found"),
}
// NO COLLISION CHECKS, OLD VALUE OVERWRITTEN
btreemap.insert(3, "number 9898798");
let new_value = btreemap.get(&3);
match new_value {
Some(v) => println!("b_key 3 new value: {}", v),
None => println!("no value changed"),
}
// add collision check manually:
println!("[*] checking possible collisions on b_key 1");
if !btreemap.contains_key(&1) {
btreemap.insert(1, "number 1 not present in btreemap => insertion done");
}
else {
println!("[!] b_key 1 already present ... no changes applied")
}
for kvp in btreemap.iter() {
println!("b_key: {} b_value: {}", kvp.0, kvp.1);
}
}
/*
h_key: 3 h_value: number 3
h_key: 2 h_value: number 2
h_key: 1 h_value: number 1
b_key: 1 b_value: number 10
b_key: 2 b_value: number 11
b_key: 3 b_value: number 12
b_key 3 old value: number 12
b_key 3 new value: number 9898798
[*] checking possible collisions on b_key 1
[!] b_key 1 already present ... no changes applied
b_key: 1 b_value: number 10
b_key: 2 b_value: number 11
b_key: 3 b_value: number 9898798 */
structures
Structs - data structure containing key:value pairs Can be used in other structs.
fn main() {
struct Car {
make: String,
model: String,
year: u32,
price: f64,
}
let mut huracan: Car = Car {
make: String::from("lambo"),
model: String::from("huracan"),
year: 2020,
price: 320_000.00
};
println!("the cost of a {} {} {} is ${}", huracan.year, huracan.make, huracan.model, huracan.price);
}
impl & associated functions
impl
used to define custom implementation on a type (here on “Person” struct). Linking functions to structs: can be standalone calls like Foo::bar();
.
If self
keyword is passed in as parameter, can then be called like foo.bar()
struct Person {
first_name: String,
last_name: String,
date_of_birth: u16,
status: Status
}
enum Status {
ALIVE,
DEAD
}
impl Person {
fn resurrect(&mut self) { // linking function "resurrect" to Person struct
self.status = Status::ALIVE;
}
}
fn main() {
let mut person = Person {
first_name: String::from("charlie"),
last_name: String::from("chaplin"),
date_of_birth: 1889,
status: Status::DEAD
};
person.resurrect();
println!("brought back {} {}!", person.first_name, person.last_name);
match person.status {
Status::ALIVE => println!("{} {} is alive hooray!", person.first_name, person.last_name),
Status::DEAD => println!("{} {} is dead!", person.first_name, person.last_name)
}
}
The linked functions can also return data:
fn main() {
struct Rectangle {
width: u16,
length: u16
}
let rect = Rectangle { width: 30, length: 12 };
let rect_area = rect.width * rect.length;
println!("first rectangle of width {} length {} has an area of {}", rect.width, rect.length, rect_area);
// defines an "area" function/method associated/tied to the "Rectangle" struct
impl Rectangle {
fn area(&self) -> u16 { // = rect.area()
self.width * self.length
}
}
// "self" keyword passed to "area" function allows calling of rect.area() method
println!("first rectangle of width {} length {} has a rect.area of {}", rect.width, rect.length, rect.area());
impl Rectangle {
fn new(length: u16, width: u16) -> Self {
Self {
length,
width
}
}
fn calculate_area(&self) -> u16 {
self.width * self.length // = return self.length * self.width;
}
}
let second_rect = Rectangle::new(10, 40);
let second_area = second_rect.calculate_area();
println!("second rectangle of width {} length {} has an area of {}", second_rect.length, second_rect.width, second_area);
}
/*
first rectangle of width 30 length 12 has an area of 360
first rectangle of width 30 length 12 has a rect.area of 360
second rectangle of width 10 length 40 has an area of 400 */
enums / or enumeration
A type representing a set of named values
use std::io;
fn main() {
#[derive(Debug)] // implement `Debug` trait into Direction
enum Direction {
Up,
Down,
Left,
Right,
}
let up = Direction::Up;
let down = Direction::Down;
impl Direction {
fn opposite(&self) -> Direction {
match *self {
Direction::Up => Direction::Down,
Direction::Down => Direction::Up,
Direction::Left => Direction::Right,
Direction::Right => Direction::Left,
}
}
}
let direction = Direction::Up;
let opposite_direction = direction.opposite();
#[derive(Debug)] // without this, when we println circle its gonna crash
enum Shape {
Circle(f32),
Rectangle(f32, f32),
}
let circle = Shape::Circle(10.0);
let rectangle = Shape::Rectangle(32.0, 12.0);
println!("{:?}", circle);
}
/* Circle(10.0) */
traits
Like interfaces in other languages.
Enforces a given type to implement a set of methods - allows to write generic code allowing to work with wide range of types - agnostic code - re-usable code acorss different types
fn main() {
trait Damage { // 'Damage' interface that datatypes can implement
fn damage(self: &mut Self);
}
#[derive(Debug)]
struct HP {
hp_remaining: i32, // one type: hp_remaining
}
impl Damage for HP { // implement the TRAIT into the struct with this
fn damage(self: &mut Self) {
self.hp_remaining -= 1;
}
}
let mut hp = HP {hp_remaining: 100};
hp.damage();
println!("[!] you took a hit ! {:?}", hp);
hp.damage();
println!("[!] you took a hit ! {:?}", hp);
hp.damage();
println!("[!] you took a hit ! {:?}", hp);
hp.damage();
println!("[!] you took a hit ! {:?}", hp);
///////////////////////////////////////////////////////////////////////////////
trait Drawable {
fn draw(&self);
}
#[derive(Debug)]
struct Circle {
radius: f32,
}
impl Drawable for Circle {
fn draw(&self) {
println!("[*] drawing a circle of radius {}", self.radius);
}
}
fn draw_shape<T: Drawable>(shape: &T) {
shape.draw();
}
let circle = Circle {radius: 10.2};
circle.draw();
println!("{:?}", circle);
draw_shape(&circle);
println!("{:?}", circle);
}
/*
[!] you took a hit ! HP { hp_remaining: 99 }
[!] you took a hit ! HP { hp_remaining: 98 }
[!] you took a hit ! HP { hp_remaining: 97 }
[!] you took a hit ! HP { hp_remaining: 96 }
[*] drawing a circle of radius 10.2
Circle { radius: 10.2 }
[*] drawing a circle of radius 10.2
Circle { radius: 10.2 } */
ownership / moving ownership
In Rust, memory is managed through a system of ownership and borrowing. Allows freeing of memory for the allocated value. Each value in Rust has an owner, which is responsible for managing the memory used by that value. When a value goes out of scope, its memory is automatically freed. This eliminates the need for manual memory management or garbage collection, which can lead to bugs, performance issues, and security vulnerabilities.
Stack vs Heap:
Stack is fast. Values are stored in order and all are fix-sized. Uses LIFO.
Heap is slower. Values are unordered and of a variable size.
The heap uses a return address for requested space called a pointer.
Can force moving ownership with move
(see concurrency part example).
fn main() {
let name = String::from("toto"); // name owns "toto"
let new_new_name = name.clone(); // clone returns a copy or name
let new_name = name; // "toto" ownership is passed to new_name
// let new_name = &name; => no ownership issue ("&" allows to associate the address of "name" to "new_name")
// let new_name = name.clone() => no ownership issue (clone implement copy trait)
println!("hello my name is {}", new_name);
println!("hey mein name ist {}", new_new_name);
}
/*
hello my name is toto
hey mein name ist toto */
ownership
3 rules:
Each value has an owner (owned by a variable) || There can only be one owner at a time || When the owner goes out of scope, the memory becomes free.
fn main() {
// message_1 "content" (stored on the stack) is a pointer to the address of "hello world".
// The String value ("hello world") is stored on the heap.
let message_1 = String::from("hello world");
// --------- move occurs because `message_1` has type `String`, which does not implement the `Copy` trait
println!("{}", message_1);
let message_2 = message_1;
// --------- value moved here: we moved the address of message_1 into message_2
println!("{}", message_1);
// ^^^^^^^^^ value borrowed here after move: because we moved the address of message_1, we dont know where to find it
}
/*
error[E0382]: borrow of moved value: `message_1`
--> src/main.rs:6:20 */
borrowing
Allows another variable to reference the data, without deallocating the data (previous example) using pointers.
Pointer to data in memory using &
, prints its address using :p
fn main() {
let mut message_1 = String::from("hello world");
println!("message_1: {}", message_1);
let message_2 = &message_1; // message_2 now contains "hello world" heap memory address and message_1 hasn't been moved
println!("message_2: {}", message_2);
println!("message_2 content: {:p}", message_2);
println!("message_1 lives at: {:p}", message_2);
let message_3 = &mut message_1;
*message_3 = String::from("toto");
println!("message_1 now contains pointer to: {}", message_1);
println!("message_1 still lives at: {:p}", &message_1);
}
/*
message_1: hello world
message_2: hello world
message_2 content: 0x16d3525a0
message_1 lives at: 0x16d3525a0
message_1 now contains pointer to: toto
message_1 still lives at: 0x16d3525a0 */
// passing ownership (moving ownership, no memory reallocation or data copy) to functions
fn take_ownership(s: String) {
// s comes into scope
println!("{}", s); // s is valid
// s is dropped here
}
fn main() {
let s = String::from("hello");
take_ownership(s);
// println!("{}", s); // Error: s is invalid here
}
// returning ownership
fn return_ownership() -> String {
let s = String::from("hello");
return s; // ownership is moved to the caller
}
fn main() {
let s = return_ownership();
println!("{}", s); // s is valid
references & borrowing
Using a value without taking ownership - mutable reference can modify the value referenced
fn main() {
let a = String::from("toto"); // a owns String "toto"
let b = &a; // a borrow to a (immutable)
println!("name is {}", b); // b can read a
let s1 = String::from("hello"); // s1 owns String hello
let len = compute_length(&s1); // pass a reference to s1 into function
println!("the length of {} is {}", s1, len);
fn compute_length(s: &String) -> usize { // s is a reference to a String value
s.len() // returns length of string pointed to by s
} // s goes out of scope, but because it's a reference and doesn't own the string, nothing happens
let mut x = 10;
let y = &mut x;
println!("y: {}", *y);
*y += 1; // works here
println!("x: {}", x); // immutable borrow occurs here
// *y += 1;
x += 1;
println!("{}", x);
}
/*
name is toto
the length of hello is 5
y: 10
x: 11
12 */
input/output
user input
use std::io;
fn main() {
println!("who goes there");
let mut name = String::new();
io::stdin().read_line(&mut name);
let enter = "you may enter";
println!("hello there {}, {}", name.trim_end(), enter); // trim_end will remove the new line char induced previously
}
/*
who goes there
Rodolf Gandalf
hello there Rodolf Gandalf, you may enter */
use std::io;
use std::io::Write;
fn main() {
loop {
// println not used so user can type on the same line
print!("> ");
// stdout forced flush so '>' isn't printed with a newline
let _ = io::stdout().flush();
// create a buffer to hold the input
let mut input = String::new();
// reads from stdin
// read_line() returns a `Result` which can be used to catch errors
let _ = io::stdin().read_line(&mut input);
println!("you said: {}", input);
if input.trim_end().eq_ignore_ascii_case("exit") {
break
}
}
}
/*
> hello
you said: hello
> exit
you said: exit
## program stops */
file input & output
use std::{fs, io};
use std::fs::{File, OpenOptions}; // openoption allows us to append
use std::io::prelude::*;
fn main() {
let mut file = File::create("src/hello.txt").expect("failed to create file");
file.write_all("hello world\n".as_bytes());
file.write_all(b"hello again\n").expect("failed to write to file"); // same as above
let mut file = OpenOptions::new().append(true).open("src/hello.txt").expect("failed to write to file");
file.write_all(b"yipikayeee").expect("failed to write to file");
let mut file = File::open("src/hello.txt").expect("unable to open file");
let mut file_content = String::new();
file.read_to_string(&mut file_content).unwrap(); // read content and write into file_content
println!("{}", file_content);
fs::remove_file("src/hello.txt").expect("unable to delete the file") // deletes file
}
/*
hello world
hello again
yipikayeee */
error handling - Result, Ok, Err, ?, unwrap() and the panic! macro
Result enum builds on Option by allowing a function to return a reason for the failure. Returning a reason is more useful because it allows the caller to handle the error in more specific ways, or to return a more accurate error message to the user. Enum, Result<T, E> has two variants: Ok and Err.
Ok represents a successful computation with a value - Err represents an error with an associated message.
Instead of Result we can use ? which removes the matching and display Ok() - propagate the error of the Err variant of a Result type up the call stack.
HELPER METHODS - unwrap(), expect(): two types of errors: recoverable and unrecoverable: RECOVERABLE - can still goes on - includes result enum & option enum |||| UNRECOVERABLE - crash - includes panic! & panic macro
unwrap() - returns the value of the “Ok()” variant of a “Result” type.
panic! - panic with a custom error message
Panic
Rust encounters runtime error => panic. This code will compile (vectors are flexible: size can change at runtime), but throws a panic when running:
fn main() {
let vector = vec![0; 5];
println!("vector[5]: {}", vector[5]);
}
/*
thread 'main' panicked at src/main.rs:3:37:
index out of bounds: the len is 5 but the index is 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace */
Option
Vectors have get()
function returning enum Option<T>
: two values None
and Some
.
T
= generic - can return any datatype inside this enum. Use with Match
for error handling:
fn main() {
let vector = vec![1,2,3];
for i in 0..=3 {
let option = vector.get(i);
match option {
None => println!("no data found at index {}", i),
Some(d) => println!("data at index {}: {}", i, d)
}
}
}
/*
data at index 0: 1
data at index 1: 2
data at index 2: 3
no data found at index 5 */
Result
Result enum built on Option
: allows a function to return a reason for failure which allows the caller to handle the error in wanted/specific way.
use std::fs;
fn main() {
// Result<T, E> variants: `Ok` and `Err`
let result = fs::File::open("./test.txt");
match result {
Ok(_) => println!("file opened"),
Err(e) => println!("error opening file: {}", e)
}
}
/* file opened */
Error propagation
use std::fs::File;
use std::io::{Error, Read};
fn open_file(path: &str) -> Result<File, Error> { // returns Result<T,E>
File::open(path) // eliminate need for `match` + returns Result<File> directly
}
fn read_file(path: &str) -> Result<String, Error> { // returns Result<T,E>
let mut file = open_file(path)?; // if check fail ('?') sends error to main()
let mut buf = String::new();
let _ = file.read_to_string(&mut buf); // if no error, reads the file
Ok(buf)
}
fn main() { // only function to need a match
let result = read_file("./test.txt");
match result {
Ok(content) => println!("{}", content),
Err(error) => println!("error: {}", error)
}
}
/* error: No such file or directory (os error 2) */
Variant:
use std::fs::File;
use std::io::{Error, Read};
fn open_file(path: &str) -> Result<File, Error> { // returns Result<T,E>
File::open(path) // eliminate need for `match` + returns Result<File> directly
}
fn read_file(path: &str) -> Result<String, Error> { // returns Result<T,E>
let mut file = open_file(path)?; // if check fail ('?') sends error to main()
let mut buf = String::new();
let _ = file.read_to_string(&mut buf); // if no error, reads the file
Ok(buf)
}
fn main() {
let result1 = read_file("./test.txt");
match result1 {
Ok(content) => println!("{}", content),
Err(error) => println!("error: {}", error)
}
// if no file present, no panic, keeps going:
let result2 = read_file("./test.txt");
match result2 {
Ok(content) => println!("bidi {}", content),
Err(error) => println!("bidi error: {}", error)
}
}
/* error: No such file or directory (os error 2)
bidi error: No such file or directory (os error 2) */
use std::{f32::consts::E, fs::File, io::Read};
fn divide(x: i32, y: i32) -> Result<i32, String> {
if y == 0 {
return Err(String::from("cant divide by zero"));
}
Ok(x / y)
}
fn divide2(x: i32, y: i32) -> i32 {
if y == 0 {
panic!("cannot divide by zero");
}
x / y
}
fn read_file(path: &str) -> Result<String, std::io::Error> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
fn main() {
let result = divide(10, 2);
match result {
Ok(value) => println!("Result: {}", value),
Err(msg) => println!("Error: {}", msg),
}
let result = divide(10, 0); // throw an error but program continues
match result {
Ok(value) => println!("Result: {}", value),
Err(msg) => println!("Error: {}", msg),
}
println!("the show must go on!");
let result = divide(10, 2).unwrap(); // case match so all good
println!("Result:{}", result);
/* let result = divide(10, 0).unwrap(); // case doesnt match, panic
println!("Result:{}", result);
let result = divide(10, 0).expect("divide by zero error");
println!("Result:{}", result); // case doesnt match, panic */
let result = divide2(10, 2);
println!("Result: {}", result);
let result = read_file("src/test.txt"); // clean error no panic
match result {
Ok(contents) => println!("File contents: {}", contents),
Err(err) => println!("Error reading file: {}", err),
}
}
/*
Result: 5
Error: cant divide by zero
the show must go on!
Result:5
Result: 5
File contents: hello world */
Debugging
dbg!
maccro: quick/dirty debugging. Printing content with dgb!
:
use std::fs;
fn main() {
let vector = vec![1,2,3];
dbg!(vector);
}
/* [src/main.rs:5:5] vector = [
1,
2,
3,
] */
Managing error:
use std::fs;
fn main() {
let file_handle = fs::File::open("./test.txt");
match file_handle {
Ok(_) => println!("file opened"),
Err(e) => { dbg!(e); }
}
}
/* [src/main.rs:7:21] e = Os {
code: 2,
kind: NotFound,
message: "No such file or directory",
} */
Generics & bounds
Generics: a type representing values of any types - add flexibility - remove the need to declare type early on.
This function only accept and returns u8
:
fn add_integers(int_1: u8, int_2: u8) -> u8 {
return int_1 + int_2;
}
Can take and return any type using generic + a bound: specify requirement + T
Ouput
datatype:
use std::ops::Add;
// <T: Add<Output = T>> is a bound: ensure datatypes that are passed can be added
// specifying that T needs to support the Add trait
fn add_integers <T: Add<Output = T>> (int_1: T, int_2: T) -> T {
// only those two need to be of same type:
return int_1 + int_2;
}
fn main() {
let result = add_integers(8, 3);
println!("{}", result);
let result = add_integers(823497209, 92070);
println!("{}", result);
}
/*
11
823589279 */
Can be done using Where
:
fn add_integers(int_1: T, int_2: T) -> T
where T: Add<Output = T> {
// only those two need to be of same type:
return int_1 + int_2;
}
fn main() {
let result = add_integers(8, 3);
println!("{}", result);
let result = add_integers(823497209, 92070);
println!("{}", result);
}
/*
11
823589279 */
fn main() {
struct Items<T> {
x: T,
y: T,
}
let i = Items{
x:1.0, y: 2.0
};
println!("x: {}, y: {}", i.x, i.y);
}
/* x: 1, y: 2 */
Closures
Function that doesn’t have a name and is declared inline within an existing function. Declared using two pipes, || followed by {}. Can capture the enclosing environment - convenient for on the fly usage - allow to use variable outside of closure scope. Both input and return types can be inferred and input variable names must be specified. Regular functions can’t refer to variables in the enclosing environment
fn main() {
// closures are anonymous
|| {
// closure declared inside main()
};
// assigning closure to a variable to execute it
let closure = |message| {
println!("{}", message);
};
closure("hello world!");
// for data to be returned by a closure we need to indicate the return type
let closure_two = |name: &str| -> String {
format!("hello {}", name)
};
let message = closure_two("Rasta");
println!("{}", message);
}
/*
hello world!
hello Rasta */
fn main() {
let outer_var = 42;
// uncomment this and compiler will throw an error + suggest that we define a closure instead.
/* fn function(inside_var: i32) -> i32 {
inside_var + outer_var
}*/
// Annotation is identical to function annotation but is optional
// as are the `{}` wrapping the body. These nameless functions
// are assigned to appropriately named variables.
let closure_annotated = |inside_var: i32| -> i32 {
inside_var + outer_var
};
// The return type is inferred.
let closure_inferred = |inside_var| inside_var + outer_var ;
// Call the closures.
println!("closure_annotated: {}", closure_annotated(1));
println!("closure_inferred: {}", closure_inferred(1));
// Once closure's type has been inferred, it cannot be inferred again with another type.
//println!("cannot reuse closure_inferred with another type: {}", closure_inferred(42i64)); // this would break
println!("cannot reuse closure_inferred with another type: {}", closure_inferred(42i32)); // this not
// A closure taking no arguments which returns an `i32`. Return type is inferred
let one = || 1;
println!("closure returning one: {}", one());
}
Concurrency
Easiest way: creates new thread with thread::spawn()
(returns a JoinHandle
) and a closure ||
. Brings blocage: code executed in thread wont keep going until thread is done.
Threads
Thread that doesnt return a value:
use std::thread;
fn main() {
let handle = thread::spawn( || { // JoinHandle<()>
println!("hello from thread!");
});
let result = handle.join(); // Result<()>
match result {
Ok(_) => println!("thread finished successfully"),
Err(_) => println!("thread didnt finish successfully")
}
}
/*
hello from thread!
thread finished successfully */
Thread that returns a value + ownership moved from value
to handle
closure so it can use it in its scope:
use std::thread;
fn main() {
let value = 20;
let handle = thread::spawn(move || -> i32 {
value * 2
});
let result = handle.join();
match result {
Ok(res) => println!("result: {}", res),
Err(_) => println!("thread didnt finish successfully")
}
}
/* result: 40 */
Channels
Can pass messages in and out of threads with channels - can be used as ‘signals’ to indicate something is treated/done. mspc
= multi-producer, single-consumer - multiple parties can send data into channel but only one should receive.
Creating one sender and one receiver:
use std::{sync::mpsc, thread};
fn main() {
let (snder, rcver) = mpsc::channel();
let _thread = thread::spawn( move || {
let _first_sender = snder.send("hello from thread!");
});
match rcver.recv() {
Ok(message_received) => println!("{}", message_received),
Err(_) => {}
}
}
/* hello from thread! */
If we add another sender, but dont add another receiver, second message is sent but not received/printed:
use std::{sync::mpsc, thread};
fn main() {
let (snder, rcver) = mpsc::channel();
let _thread = thread::spawn( move || {
let _first_sender = snder.send("hello from thread!");
let _second_sender = snder.send("second sender here alloooo");
});
match rcver.recv() {
Ok(message_received) => println!("{}", message_received),
Err(_) => {}
}
}
/* hello from thread! */
Fix:
use std::{sync::mpsc, thread};
fn main() {
let (snder, rcver) = mpsc::channel();
let _thread = thread::spawn( move || {
let _first_sender = snder.send("hello from thread!");
let _second_sender = snder.send("second sender here alloooo");
});
match rcver.recv() {
Ok(message_received) => println!("{}", message_received),
Err(_) => {}
}
match rcver.recv() {
Ok(message_received) => println!("{}", message_received),
Err(_) => {}
}
}
/* hello from thread!
second sender here alloooo */
Challenges
challenge 1
const BIRTHDAY: u32 = 1;
fn main() {
/* Challenge 1 - Build a program that has the following:
1) Has a global constant integer named 'birthday' with a value of 1
2) Has a local string variable named 'my_name' with your name as the value
3) Has a local string variable named 'my_birthday' with your birth month/day (no year) as the value
4) Has a local mutable integer variable named 'age' with your current age as the value
5) Has a local integer variable named 'new_age' with your age after your birthday as the value
6) Prints out 'My name is X and I am X years old. I will turn X on X'
*/
let my_name: &str = "john";
let my_birthday: &str = "april";
let mut age = 38;
let new_age = age + BIRTHDAY;
println!("my name is {} and im {} years old. i will turn {} on {}", my_name, age, new_age, my_birthday);
}
challenge 2
use std::io::{self, Read};
fn main() {
/* Build a simple calculator that takes two user inputs
then calculates the addition, subtraction, multiplication, and division
of those two inputs.
*/
// DEFINE X
println!("give me some numbers so i can math on them!");
let mut x = String::new();
io::stdin().read_line(&mut x);
let x: i32 = x.trim().parse().expect("input not an integer");
let x_float = x as f32;
// DEFINE Y
let mut y = String::new();
io::stdin()
.read_line(&mut y)
.expect("failed to read number");
let y: i32 = y.trim().parse().expect("input not an integer");
let y_float = y as f32;
println!("{} + {} = {}", x, y, x + y);
println!("{} - {} = {}", x, y, x - y);
println!("{} * {} = {}", x, y, x * y);
println!("{} / {} = {}", x, y, x / y);
println!("{} / {} = {}", x_float, y_float, x_float / y_float);
//println!("{} ^{} = {}", x, y, i32::pow(x, y));
println!("{} ^{} = {}", x, y, i32::pow(x.try_into().unwrap(), y.try_into().unwrap())); // convert a i32 into u32
}
/*
give me some numbers so i can math on them!
3
4
3 + 4 = 7
3 - 4 = -1
3 * 4 = 12
3 / 4 = 0
3 / 4 = 0.75
3 ^4 = 81 */
challenge 3
use std::{i32, io::{self, Read}, os::unix::fs::PermissionsExt};
use std::cmp::Ordering;
fn add(x: i32, y: i32) -> i32 {
x + y
}
fn sub(x: i32, y: i32) -> i32 {
x - y
}
fn mul(x: i32, y: i32) -> i32 {
x * y
}
fn div(x: f32, y: f32) -> f32 {
x / y
}
fn main() {
// Create a calculator that takes three user inputs (x, y, and operator)
// Create functions for +, -, *, /
// Use if/else or Match for operator
// Might take a little research!
println!("Welcome to ZE calculator lets calculate some calcultations shall we");
println!("give me a first number: ");
let mut input_x = String::new();
io::stdin().read_line(&mut input_x);
let input_x: i32 = input_x.trim().parse().expect("not an integer, try again");
let x_float = input_x as f32;
println!("give me an operator: +, -, *, or /");
let mut operator = String::new();
io::stdin().read_line(&mut operator);
let operator: &str = operator.trim();
println!("give me a second number: ");
let mut input_y = String::new();
io::stdin().read_line(&mut input_y);
let input_y: i32 = input_y.trim().parse().expect("not an integer, try again");
let y_float = input_y as f32;
match operator {
"+" => println!("result: {}", add(input_x, input_y)),
"-" => println!("result: {}", sub(input_x, input_y)),
"*" => println!("result: {}", mul(input_x, input_y)),
"/" => println!("result: {}", div(x_float, y_float)),
_ => println!("not an operator!")
};
}
/*
Welcome to ZE calculator lets calculate some calcultations shall we
give me a first number:
3
give me an operator: +, -, * or /
/
give me a second number:
6
result: 0.5 */
/*
Welcome to ZE calculator lets calculate some calcultations shall we
give me a first number:
3
give me an operator: +, -, * or /
a
give me a second number:
6
not an operator!*/
Projects
Password generator
Generates a 40 char password
use rand::Rng;
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
0123456789)(*&^%$#@!~+-=_[]{}|";
const PASSWORD_LEN: usize = 40;
fn main() {
let mut rng = rand::thread_rng();
let password: String = (0..PASSWORD_LEN)
.map(|_| {
let idx = rng.gen_range(0..CHARSET.len());
CHARSET[idx] as char
})
.collect();
println!("{:?}", password);
}
SHA 256 Password Cracker
use std::{
env,
fs::File,
io::{self, BufRead},
path::Path,
process::exit,
};
use sha2::{Digest, Sha256};
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where P: AsRef<Path>,
{
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 3 {
println!("Wrong number of arguments given !");
println!("Usage: cargo run <hashed password> <password file>");
exit(1);
}
let wanted_hash = &args[1];
let password_file = &args[2];
if let Ok(password_list) = read_lines(password_file) {
for (attempt, password) in password_list.flatten().enumerate() {
let password = password.trim();
let password_hash = format!("{:x}", Sha256::digest(&password));
if password_hash == *wanted_hash {
println!("hash cracked at attempt {} || hash: {} || password: {:?}", attempt + 1, wanted_hash, password);
};
}
} else {
println!("Failed to open password file");
exit(1);
}
}
Improved version:
use std::{
env,
fs::File,
io::{self, BufRead},
path::Path,
process::exit,
};
use sha2::{Digest, Sha256};
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where P: AsRef<Path>,
{
let file = File::open(filename)?;anything that the Rust compiler spits out
Ok(io::BufReader::new(file).lines())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = env::args().collect();
if args.len() != 3 {
println!("Wrong number of arguments given !");
println!("Usage: cargo run <hashed password> <password file>");
exit(1);
}
let wanted_hash = &args[1];
let password_file = &args[2];
let password_list = read_lines(password_file)?;
let result = password_list
.map_while(Result::ok)
.enumerate()
.find_map(|(i, password)| {
let password_hash = format!("{:x}", Sha256::digest(password.trim()));
if password_hash == *wanted_hash {
Some((i, password_hash))
} else {
None
}
});
if let Some((attempt, password)) = result {
println!(
"hash cracked at attempt: {attempt} || hash: {wanted_hash} || password: {password:?}",
);
}
Ok(())
}
Some of the ressources used:
- SHA-2 Crate on crates.io
- Reading Lines from a File in Rust (Rust by Example)
- SHA-2 Documentation on docs.rs
- Hex Literal Crate on docs.rs (Note: The
hex!
macro works at compile time and cannot take runtime variables, which might cause crashes if misused.) - BufReader Struct Documentation
URL shortener
use std::{
env,
fs::{File, OpenOptions},
io::{self, stdin, BufRead, BufReader, Write},
process::exit
};
use rand::{
self,
distributions::Alphanumeric,
thread_rng,
Rng
};
const URL_LENGTH: usize = 8;
fn show_usage() {
println!("Usage: cargo run <www.example.com> <urls_file.txt>");
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 3 {
println!("Wrong numbers of arguments given.");
show_usage();
exit(1);
}
let url_passed = &args[1];
let urls_path = &args[2];
// open file, if doesnt exist, offer user the possibility to create it
let mut urls_file = match OpenOptions::new().append(true).open(urls_path) {
Ok(file) => file,
Err(_) => {
println!("Error opening the file: file doesn't exist");
print!("Do you want to create the file? (yY) or (nN) > ");
let _ = io::stdout().flush();
let mut input = String::new();
let _ = stdin().read_line(&mut input);
if input.trim_end().eq_ignore_ascii_case("y") {
let urls_file = File::create(urls_path).expect("Failed to create file");
println!("File created at {}", urls_path);
urls_file
}
else if input.trim_end().eq_ignore_ascii_case("n") {
println!("Ok, exiting");
exit(1);
}
else {
println!("Wrong choice, exiting");
exit(1);
}
}
};
// shorten URL if long
if url_passed.starts_with("www") {
let short_url: String = thread_rng()
.sample_iter(&Alphanumeric)
.take(URL_LENGTH)
.map(char::from)
.collect();
println!("'{}' shortened into '{}'", short_url, url_passed);
// map urls and write them into mapping file
let mapped_urls = format!("{},{}", short_url, url_passed);
write!(urls_file, "{}\n", mapped_urls).expect("Failed to write to file");
}
else if url_passed.len() != URL_LENGTH {
println!("Please enter valid Url");
show_usage();
exit(1);
}
// if short url is given
else {
let mut found_match = false;
let urls_file = match File::open(urls_path) {
Ok(file) => file,
Err(_) => {
println!("Error reading the urls file");
return;
}
};
// if file could be opened, create a reader to parse lines
let reader = BufReader::new(urls_file);
for line in reader.lines() {
let mapping = match line {
Ok(line) => line,
Err(_) => {
println!("Error reading mapping file");
continue;
}
};
// create iterable vector to store lines content
let parts: Vec<&str> = mapping.split(',').collect();
// if not two urls, dont care
if parts.len() != 2{
continue;
}
// if two urls, look for match
else {
let short_url = parts[0];
let long_url = parts[1];
if url_passed == short_url {
println!("[*] Match: {} gives: {}", url_passed, long_url);
found_match = true;
}
else {
continue;
}
}
}
if !found_match {
println!("[!] No match found for: {}", url_passed);
}
}
}
/*
Concepts used:
When taking user input:
- The standard output (stdout) stream is line-buffered, which means text isn't usually flushed to the console until a newline appears.
Since we want the > character to appear without printing a new line, we have to manually flush stdout so that it appears.
- We used "trim_end() on user input to remove the new line char added (its a string, it ends with a newline)"
*/
Quizz game
use std::{
env,
io::{self, stdin, Write},
process::exit
};
fn show_usage() {
println!("Usage: cargo run");
}
fn show_welcome_message() {
println!("Hey whats your name?");
print!("> ");
let _ = io::stdout().flush();
}
fn check_correctness(correct_choice: &str) -> bool {
let _ = io::stdout().flush();
let mut user_answer = String::new();
let _ = stdin().read_line(&mut user_answer);
if user_answer.trim().eq_ignore_ascii_case(correct_choice) {
println!("Correct!");
true
}
else {
println!("Wrong!");
false
}
}
fn show_question_1() {
println!("1. What is the capital city of France?");
println!("A. London");
println!("B. Paris");
println!("C: Rome");
print!("Your answer: ");
let _ = io::stdout().flush();
}
fn show_question_2() {
println!("\n2. What is the largest country in the world by area?");
println!("A. Russia");
println!("B. Canada");
println!("C: China");
print!("Your answer: ");
let _ = io::stdout().flush();
}
fn show_question_3() {
println!("\n3. Who is credited with inventing the World Wide Web?");
println!("A. Bill Gates");
println!("B. Tim Berners-Lee");
println!("C. Steve Jobs");
print!("Your answer: ");
let _ = io::stdout().flush();
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 1 {
println!("Wrong numbers of arguments given");
show_usage();
exit(1);
}
let total_answers = 3;
let mut correct_answers = 0;
show_welcome_message();
let mut user_name = String::new();
let _ = stdin().read_line(&mut user_name);
if user_name.trim().len() == 0 {
println!("[!] No data entered, please enter a user name");
exit(1);
}
else {
println!("Welcome to the game {}", user_name.trim());
}
show_question_1();
if check_correctness("B") {
correct_answers += 1;
}
show_question_2();
if check_correctness("A") {
correct_answers += 1;
}
show_question_3();
if check_correctness("B") {
correct_answers += 1;
}
let percentage = (correct_answers as f32 / total_answers as f32) * 100.0;
println!("Your score: {:.2}%", percentage);
}
Some concepts:
- Crate: anything that the Rust compiler spits out, more often used to describe librairies.
- Rust has two data type subsets: scalar (holds a single value - integers, chars, floats and booleans), and compound (can hold multiple values aka collections - arrays and tuples).
- Data types => “primitive”, built into Rust’s standard library and stored on the stack.
- Own custom data structures => stored on the heap.
- stack allocated data => inexpensive to copy: integers (i32, u64), booleans (bool), floating-point numbers (f32, f64), and character types (char) implement the
Copy
trait. - heap allocated data => ownership matters: data like
String
orVec<T>
don’t implementCopy
(size can change at runtime).String
implementClone
(reallocate memory for the clone). - structs, enums, impl blocks, etc. can be declared in (almost) any order within the same scope because compiler processes the entire module as a unit.
- Rust forces user to declare how data is shared between threads
- Concurrency pit falls:
- when multiple threads try to modify the same object at the same time;
- when one thread locks access to an object, thus preventing another thread from accessing it.
- when multiple threads are waiting a resource that they each have (deadlocks).
- Resource Acquisition Is Initialization (RAII): when an object is created, it acquires resources; when it goes out of scope, it releases them automatically: when owner goes out of scope, drop is called to free memory (no dangling, no double-free).
- getter => collect the data, setter => define the data.
pub
allows target to be called from outside the module.
Questions:
If we pass two u8 values that when added together result in more than the maximum value:
if the binary is compiled in Debug mode: cause a runtime panic.
if compiled in Release mode: will perform a rollover: it will rollover from it’s highest value, 255 back to 0.
Acknowledgments
Thanks Nicolas Haquet for having clarified some concepts i had issues to fully grasp and for the improved project 1 script. —