Compare commits

..

10 Commits

21 changed files with 2461 additions and 36 deletions

1645
Cargo.lock generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -2,5 +2,11 @@
name = "rust-by-example"
version = "0.1.0"
edition = "2021"
default-run = "rust-by-example"
[dependencies]
rand = "0.9.2"
reqwest = "0.12.22"
# clap = "2.27.1" # from crates.io
# rand = { git = "https://github.com/rust-lang-nursery/rand" } # from online repo
# bar = { path = "../bar" } # from a path in the local filesystem

View File

@@ -1 +1 @@
# Rust by example
# [Rust by example](https://doc.rust-lang.org/stable/rust-by-example/)

View File

@@ -0,0 +1,4 @@
fn main() {
println!("This was ran with '$ cargo run --bin secondary_main'");
println!("This is a secondary main!");
}

View File

@@ -0,0 +1,14 @@
fn box_integer(number: i32) -> Box<i32> {
Box::new(number)
}
fn box_function() -> Box<fn(i32) -> Box<i32>> {
Box::new(box_integer)
}
pub fn box_basics() {
let boxed_int = box_integer(3);
let boxed_boxer = box_function();
let another_boxed_int = boxed_boxer(4);
print!("Box basics: {} {}", boxed_int, another_boxed_int);
}

3
src/concepts/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
mod box_basics;
pub use box_basics::box_basics;

View File

@@ -195,4 +195,45 @@ pub fn control_flow_module() {
n @ 1..5 => println!("{n} is between 1 and 5"),
_ => (),
}
// If let
// Lets you simplify destructuring
// Usefull if objecto neither implements nor derives PartialEq (cant do variable == Object::A)
if let n @ 1..5 = another_number {
println!("{n} is between 1 and 5");
}
// can also be used to match an enum
enum Fooo {
A,
B,
Another(u32),
};
let a = Fooo::A;
let b = Fooo::Another(3);
if let Fooo::A = a {
println!("a is A");
}
if let Fooo::Another(n) = b {
println!("b is another({n})");
}
// Let else
//<span class="underline"> Acts as a try - catch when delcaring variables
// while let
// Similar to if let, to make match sequences more tolerable
let mut optional = Some(0);
while let Some(i) = optional {
if i > 9 {
println!("Greater than 9");
optional = None;
} else {
optional = Some(i + 1);
}
}
}

View File

@@ -0,0 +1,65 @@
pub fn binary_search<T>(list: &[T], value: T) -> Result<usize, &'static str>
where
T: PartialOrd + Clone,
{
if !is_sorted(list) {
return Err("Input list must be sorted");
}
let mut current_slice = list;
let mut original_index: Option<usize> = None;
while !current_slice.is_empty() {
let middle = current_slice.len() / 2;
if current_slice[middle] == value {
let result = match original_index {
Some(i) => i + middle,
None => middle,
};
return Ok(result);
}
if middle < 1 {
return Err("Not found");
}
if current_slice[middle] > value {
current_slice = current_slice.split_at(middle).0;
} else {
current_slice = current_slice.split_at(middle).1;
if original_index.is_none() {
original_index = Some(middle);
} else {
original_index = Some(original_index.unwrap() + middle);
}
}
}
Err("Not found")
}
pub fn is_sorted<T>(list: &[T]) -> bool
where
T: PartialOrd,
{
for index in 1..list.len() {
if list[index] < list[index - 1] {
return false;
}
}
true
}
// TODO: read about good practices when using mutable parameters
// maybe the correct thing to do is to take an immutable one and create a copy inside the function.
pub fn bubble_sort<T>(mut list: Vec<T>) -> Vec<T>
where
T: PartialOrd,
{
for lap in 1..list.len() {
for index in (lap..list.len()).rev() {
if list[index] < list[index - 1] {
list.swap(index, index - 1);
}
}
}
list
}

View File

@@ -0,0 +1,66 @@
/*
*
Exercise: Implement a Multithreaded Web Crawler
#### Objective:
Create a simple multithreaded web crawler in Rust that can visit a set of web pages concurrently and extract all the links from those pages.
#### Requirements:
1. **Concurrent Fetching:** The crawler should utilize multiple threads to fetch web pages to improve performance.
2. **Link Extraction:** For each page visited by the crawler, extract all the hyperlinks (`<a href="...">`) present on the page.
3. **Avoid Cycles:** Ensure the crawler doesn't visit the same page more than once.
4. **Limit Depth:** Allow the user to specify the maximum depth for crawling (i.e., the maximum number of links to follow from the initial set of given URLs).
5. **Graceful Shutdown:** Allow the crawler to be stopped gracefully upon receiving a user interrupt (like pressing Ctrl+C).
#### Steps to Implement:
1. **Set Up the Project:**
- Create a new Rust project using Cargo.
2. **Implement URL Fetching:**
- Use `reqwest` or a similar library to fetch the contents of a web page.
- Handle networking errors gracefully.
3. **Extract Links:**
- Use the `scraper` crate or implement your own HTML parsing logic to extract links from the fetched HTML content.
4. **Multithreading:**
- Utilize Rust's `std::thread` or the `tokio` runtime to spawn threads that can concurrently fetch and process web pages.
5. **Avoid Revisits:**
- Maintain a `HashSet` or similar structure to keep track of visited URLs to prevent revisiting the same page.
6. **Depth Control:**
- Use a data structure (like a queue) to manage the list of URLs to visit, and keep track of the current depth level for each URL.
7. **Graceful Shutdown:**
- Implement signal handling to catch user interrupts and terminate the crawling process gracefully.
8. **Testing:**
- Write tests to ensure the crawler functions as expected, including edge cases like broken links, non-responsive pages, etc.
#### Hints:
- Consider using Rust's ownership model to manage shared state across threads safely.
- Make use of concurrency primitives like `Arc` and `Mutex` to safely share data between threads.
- Efficiently manage the queue of URLs to visit, making sure to handle potential deadlocks and race conditions.
This exercise will test the candidate's ability to work with network requests, concurrency, and data structures in Rust, while also evaluating their understanding of graceful error handling and software design.
*/
// #[derive(Debug)]
// struct HyperLink<'a> {
// href: &'a str,
// label: &'a str,
// }
// struct PageData<'a> {
// links: Vec<HyperLink<'a>>,
// url: &'a str,
// }
// pub fn web_crawler(url_list: Vec<&str>) -> Vec<PageData> {}
// fn get_page_links(url: &str) -> PageData {
// PageData { links: (), url }
// }
// fn get_page_body(url: &str) -> &str {}

View File

@@ -1,23 +0,0 @@
pub fn longest_palindrome(value: &str) -> String {
let mut lp: String = "0".to_string();
let input_size = value.len() - 1;
for pal_size in 2..=input_size {
for i in 0..input_size {
if input_size - i < pal_size {
break;
}
if let Some(slice) = value.get(i..(i + pal_size - 1)) {
// let reverted_slice = slice.chars().rev().collect::<String>();
// if string was mutable, we count use .reserve()
let reverted_slice = slice.chars().rev().collect::<String>();
if slice == reverted_slice {
lp = reverted_slice;
break;
}
}
}
}
lp
}

View File

@@ -0,0 +1,101 @@
/*
* Count squares
*/
pub fn count_squares(matrix: &Vec<Vec<bool>>) -> usize {
let mut square_count: usize = 0;
for j in 0..matrix.len() {
for i in 0..matrix[0].len() {
if matrix[i][j] == true {}
}
}
square_count
}
fn does_matrix_square_at(matrix: &Vec<Vec<bool>>) -> bool {}
/*
* Mirror number
*/
pub fn mirror_number(mut value: u32) -> u32 {
const BASE: u32 = 10;
let mut mirror: u32 = 0;
let size = value.to_string().len();
for index in 1..=size {
let last = value % BASE;
value = value / BASE;
mirror += last * BASE.pow(size as u32 - index as u32);
}
mirror
}
pub fn mirror_number_lazy(value: u32) -> u32 {
value
.to_string()
.chars()
.rev()
.collect::<String>()
.parse::<u32>()
.unwrap()
}
pub fn longest_palindrome(value: &str) -> String {
let mut lp: String = "0".to_string();
let input_size = value.len() - 1;
for pal_size in 2..=input_size {
for i in 0..input_size {
if input_size - i < pal_size {
break;
}
if let Some(slice) = value.get(i..(i + pal_size - 1)) {
// if string was mutable, we count use .reserve()
let reverted_slice = slice.chars().rev().collect::<String>();
if slice == reverted_slice {
lp = reverted_slice;
break;
}
}
}
}
lp
}
/*
* Quick sort
*/
pub fn quick_sort<T>(list: Vec<T>) -> Vec<T>
where
T: PartialOrd + Copy,
{
if list.is_empty() {
return list;
}
let mut pivot_list = vec![list[0]];
let pivot = pivot_list[0];
// arrange
let mut left_list: Vec<T> = Vec::new();
let mut right_list: Vec<T> = Vec::new();
for index in 1..list.len() {
let element = list[index];
if element > pivot {
right_list.push(element);
} else if element < pivot {
left_list.push(element);
} else {
pivot_list.push(element);
}
}
left_list = quick_sort(left_list);
right_list = quick_sort(right_list);
left_list.append(&mut pivot_list);
left_list.append(&mut right_list);
left_list
}

77
src/exercises/mod.rs Normal file
View File

@@ -0,0 +1,77 @@
use std::time::Instant;
mod easy_difficulty;
mod hard_difficulty;
mod medium_difficulty;
pub fn run_hard() {
println!();
}
pub fn run_medium() {
println!(
"Longest palindrome of 'asdffdas' is {}",
medium_difficulty::longest_palindrome("asdffdas")
);
let to_mirror = 12345u32;
let begin = Instant::now();
println!(
"The mirror of {} is {} and took {:?}",
to_mirror,
medium_difficulty::mirror_number(to_mirror),
begin.elapsed()
);
let begin = Instant::now();
println!(
"(lazy) The mirror of {} is {} and took {:?}",
to_mirror,
medium_difficulty::mirror_number_lazy(to_mirror),
begin.elapsed()
);
let boolean_matrix = vec![
vec![true, false, true, true],
vec![false, false, true, true],
];
println!(
"The matrix:\n{:?}\nhas {} squares inside",
boolean_matrix,
medium_difficulty::count_squares(&boolean_matrix)
);
}
pub fn run_easy() {
let list = vec![5, 1, 8, 20, 4];
// easy_difficulty::binary_search(list, 20);
// let list = easy_difficulty::slow_sort_list(list);
// println!("List ordered to {:?}", list);
println!(
"The vec {:?} is {} sorted, but [1,2,3,4] is {} sorted",
list,
easy_difficulty::is_sorted(&list),
easy_difficulty::is_sorted(&[1, 2, 3, 4])
);
let sorted_list = vec![1, 10, 15, 20, 30];
let searched = 40;
let position = easy_difficulty::binary_search(&sorted_list, searched);
match position {
Ok(pos) => println!(
"The element {searched} is indexed in {pos} at {:?}",
sorted_list
),
Err(e) => println!("Error: {}", e),
}
let bubled_list = easy_difficulty::bubble_sort(list);
println!("The original list sorted is {:?}", bubled_list);
let long_list = vec![
5, 1, 8, 20, 4, 15, 6, 7, 1, 4, 23, 9, 23, 5, 7, 7, 8, 0, 12, 4, 56, 18, 47, 23,
];
let sorted_list = medium_difficulty::quick_sort(long_list);
println!("The sorted list is: {:?}", sorted_list);
}

117
src/functions.rs Normal file
View File

@@ -0,0 +1,117 @@
fn fizzbuzz(n: i32) {
for i in 1..=n {
if i % 15 == 0 {
println!("FizzBuzz");
} else if i % 5 == 0 {
println!("Buzz");
} else if i % 3 == 0 {
println!("Fizz");
} else {
println!("{i}");
}
}
}
// Associated functions (without self) and Methods (with self)
// are the ways of connecting a function to a type.
struct Point {
x: i8,
y: i8,
}
impl Point {
// Associated functions
fn origin() -> Point {
Point { x: 0, y: 0 }
}
fn new(x: i8, y: i8) -> Point {
Point { x, y }
}
// Methods
fn run(&mut self, x: i8, y: i8) {
self.x += x;
self.y += y;
}
// bad practice, just for practice
fn to_string(&self) -> String {
format!("{} {}", self.x, self.y)
}
}
// when used as a parameter in a function
// the closures complete type must be annotated using one of a few traits.
// In order to decrease restriction, they are:
// Fn: the closure uses the captured value by reference (&T)
// FnMut: the closure uses the captured value by mutable reference (&mut T)
// FnOnce: the closure uses the captured value by value (T)
//
// NOTE: this function could also take a normal function as a parameter, not only a closure
fn apply<F>(f: F)
where
F: FnOnce(),
{
println!("About to run a closure...");
f();
}
fn create_fn() -> impl Fn() {
let text = "Fn".to_owned();
move || println!("Info: {}", text) // move converts the used variables to variables captured by
// value;
}
fn divergin_one() -> ! {
panic!("This will never return! PANIC");
}
pub fn functions_module() {
fizzbuzz(10);
println!("-- Associated functions and Methods --");
let mut my_point = Point::new(4, 5);
println!("My point: {}", my_point.to_string());
my_point.run(20, 1);
println!("My point: {}", my_point.to_string());
// Closures are made to allow access to the enclosing environment,
// similar to JS functions inside functions
let mut closure_run = |x: i8, y: i8| {
my_point.x += x;
my_point.y += y;
};
closure_run(10, 15);
println!("My point: {}", my_point.to_string());
// closure_run(10, 15);
// println!("My point: {}", my_point.to_string());
// move keyword forces the closure to take ownership of captured variables
let mut closure_run = move |x: i8, y: i8| {
my_point.x += x;
my_point.y += y;
};
// Rust provides higher order functions, such as
// - map : .map(|n| n * n)
// - filter : .filter(|&n| is_add(n))
// - take_while : .take_while(|&n| n < upper)
// Diverging functions
// Never return, marked with: !
// Useful on divergin situations where an exact type is required
for i in 0..5 {
let value: u32 = match i % 2 == 0 {
true => 5,
false => continue,
};
println!("The value is pair and {value}");
}
// NOTE: Bad example since it's not really useful
divergin_one();
}

BIN
src/lib/libmy_lib.rlib Normal file
View File

Binary file not shown.

103
src/macros.rs Normal file
View File

@@ -0,0 +1,103 @@
// Group functionality with different types
// Define special syntac for specific purpose
// Variable number of arguments
// Different combination of arguments
macro_rules! say_hello {
() => {
println!("Hello world!");
};
}
// Types of arguments have designators:
// block
// expr is used for expressions
// ident is used for variable/function names
// item
// literal is used for literal constants
// pat (pattern)
// path
// stmt (statement)
// tt (token tree)
// ty (type)
// vis (visibility qualifier)
macro_rules! generate_function {
($func_name:ident) => {
fn $func_name() {
println!("You called {:?}()", stringify!($func_name));
}
};
}
generate_function!(foo);
macro_rules! process_expression {
($expression: expr) => {
println!("{:?} = {:?}", stringify!($expression), $expression);
};
}
// Overload
macro_rules! match_block {
($left: expr; and $right: expr) => {
println!("A: {:?} and {:?}", stringify!($left), stringify!($right))
};
($left: expr; or $right:expr) => {
println!(
"B: {:?} or {:?} is {:?}",
stringify!($left),
stringify!($right),
$left || $right
)
};
}
// Repeat: variable number of args o a type
macro_rules! find_max {
($x:expr) => ($x);
($x:expr, $($y:expr), +) => (std::cmp::max($x, find_max!($($y), +)));
}
// DSLs: Domain Specific Languages
// macro_rules allows you to create your own syntax
macro_rules! calc {
(eval $e:expr) => {
println!("{} = {}", stringify!($e), $e);
};
}
macro_rules! calculate {
(eval $e: expr) => {
{
let val: usize = $e; // Force types to be integers
println!("{} = {}", stringify!{$e}, val);
}
};
(eval $e: expr, $(eval $y: expr), +) => {{
calculate!{eval $e}
calculate!{ $(eval $y), + }
}};
}
pub fn macros_rule() {
foo();
say_hello!();
process_expression!(1 + 5 * 3);
match_block!(1i32 + 1 == 2i32; and 2i32 * 2 == 4i32); // Enters first branch
match_block!(true; or false); // Enter second branch
match_block!(true; and true); // Enters first branch
println!("Max is {}", find_max!(5, 3 * 8, 4, 9 * 5));
calc! {
eval (1 + 2) * 4
}
calculate! {
eval (1 + 2) * 4,
eval 1 + 5,
eval 7 + 4,
eval 3 + 9
}
}

View File

@@ -5,15 +5,11 @@
// : /// Doc comment: generate library docs for the following item
// : //! Doc comment
// mod helloworld;
// mod primitives;
// mod customtypes;
//mod variablebindings;
// mod types;
//mod conversion;
// mod controlflow;
// mod traits;
mod str_types;
mod threads;
#[cfg(test)]
#[path = "./test/basic_test.rs"]
mod test;
fn main() {
// helloworld::hello_world_module();
@@ -23,6 +19,15 @@ fn main() {
//types::types_module();
//conversion::conversion_module();
// controlflow::control_flow_module();
//traits::traits_exercise();
str_types::str_types_module();
// traits::traits_exercise();
// str_types::str_types_module();
// my_lib::public_function();
// functions::functions_module();
// concepts::box_basics();
// operators::operators_module();
// macros_rule();
// exercises::run_easy();
// exercises::run_medium();
threads::threads_module();
}

8
src/my_lib.rs Normal file
View File

@@ -0,0 +1,8 @@
pub fn public_function() {
println!("This is a public funcion that calls a private one!");
private_function();
}
fn private_function() {
println!("This is a private function!");
}

24
src/operators.rs Normal file
View File

@@ -0,0 +1,24 @@
use std::ops;
use std::ops::Add;
/*
* Operators are syntectic sugar for method calls
* The + operator calls the add method and so on
* */
pub fn operators_module() {
let a = 3u32.add(3);
println!("a is {a}");
// Operator overloading
struct Foo;
impl ops::Add<u32> for Foo {
type Output = Foo;
fn add(self, rhs: u32) -> Self::Output {
println!("> add called with {}", rhs);
Foo
}
}
let foo = Foo;
let _too = foo + 3; // > add called with 3
}

14
src/test/basic_test.rs Normal file
View File

@@ -0,0 +1,14 @@
#[cfg(test)]
pub mod tests {
#[test]
fn test_success() {
let my_hello = "Hello world!";
assert_eq!(my_hello, "Hello world!");
}
#[test]
fn test_failure() {
let my_hello = "ef u world!";
assert_eq!(my_hello, "Hello world!");
}
}

68
src/threads.rs Normal file
View File

@@ -0,0 +1,68 @@
use std::{thread, time::Instant};
use rand::Rng;
const NTHREADS: u32 = 6;
fn generate_number_block(size: usize) -> String {
let mut block = String::new();
for _ in 0..size {
block.push_str(&rand::rng().random_range(0..9).to_string());
}
block
}
pub fn threads_module() {
let number_block = generate_number_block(999999);
let time = Instant::now();
let result_base = sum_of_block(&number_block);
let elapsed_base = time.elapsed();
println!("Functional took {}", elapsed_base.as_micros());
let time = Instant::now();
let result_parallel = sum_of_block_parallel(&number_block, 10);
let elapsed_parallel = time.elapsed();
println!("Parallel took {}", elapsed_parallel.as_micros());
if result_base == result_parallel {
println!(
"The results are the same ({}) and the time difference is {}",
result_base,
elapsed_parallel.as_micros() - elapsed_base.as_micros()
)
} else {
println!("One is wrong...");
}
}
fn sum_of_block_parallel(digits: &str, nthreads: usize) -> u32 {
let digits = digits.split_at(digits.len() / 2);
3
}
fn sum_of_block(digits: &str) -> u32 {
digits
.chars()
.into_iter()
.map(|character| character.to_digit(10).unwrap())
.sum::<u32>()
}
fn basic_thread_wait() {
let mut thread_list = vec![];
for i in 0..NTHREADS {
thread_list.push(thread::spawn(move || {
println!("this is the thread {}", i);
return 1;
}));
}
for th in thread_list {
let result = th.join();
match result {
Ok(r) => println!("Thread finished! {:?}", r),
Err(e) => println!("Thread error {:?}", e),
}
}
}

View File

@@ -6,6 +6,8 @@
* 4. Extend the trait with a second function and see what happens
* */
use std::thread::current;
pub trait Move {
fn to(&mut self, x: i32, y: i32);
}
@@ -32,4 +34,91 @@ pub fn traits_exercise() {
me.to(10, 20);
println!("My name is {} and i'm at {:?}", me.name, me.position);
// Drop trait: called with an object goes out of scope
struct Foo;
impl Drop for Foo {
fn drop(&mut self) {
println!("Foo went out of scope...");
}
}
let mut counter = 0u32;
loop {
if counter > 5 {
break;
}
counter += 1;
let _foo = Foo;
}
// Iterator traits
// Example: Fibonacci
struct Fibonacci {
current: u32,
next: u32,
}
impl Iterator for Fibonacci {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
let current = self.current;
self.current = self.next;
self.next = self.next + current;
Some(current)
}
}
let mut fib = Fibonacci {
current: 0,
next: 1,
};
for _ in 0..21 {
println!("Fib {:?}", fib.next());
}
let mut fib = Fibonacci {
current: 0,
next: 1,
};
println!("Fib 20 {:?}", fib.nth(20));
let fib = Fibonacci {
current: 0,
next: 1,
};
println!("Fib skip 20 {:?}", fib.skip(20).next());
// Disambiguating overlapping traits
struct Fee {
a: u32,
b: String,
}
trait A {
fn get(&self) -> u32;
}
trait B {
fn get(&self) -> String;
}
impl A for Fee {
fn get(&self) -> u32 {
self.a
}
}
impl B for Fee {
fn get(&self) -> String {
self.b.clone()
}
}
let fee = Fee {
a: 1234,
b: "asdf".to_string(),
};
let a = <Fee as A>::get(&fee);
let b = <Fee as B>::get(&fee);
println!("The a is {} and the b is {}", a, b);
}