From 0cf0f12f2b6472fcea206a3ab1fed80f6ec4e80b Mon Sep 17 00:00:00 2001 From: Brian Obot Date: Sun, 22 Feb 2026 16:19:10 +0100 Subject: [PATCH] Add more notes on closures and implemented a version of actix-web router struct --- src/bin/41_utility_traits.rs | 40 ++++++- src/bin/42_closures_indepth.rs | 212 +++++++++++++++++++++++++++++++++ 2 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 src/bin/42_closures_indepth.rs diff --git a/src/bin/41_utility_traits.rs b/src/bin/41_utility_traits.rs index 564fb94..53333e1 100644 --- a/src/bin/41_utility_traits.rs +++ b/src/bin/41_utility_traits.rs @@ -8,7 +8,7 @@ // - Marker traits: traits used to expression contraits on generic types that can be caputured in any other trivial way, Size and Copy // - Public Vocabulary Trait: they are not magival to the compiler but using them mirrors convetional solutions for common problems, Default, AsRed, AsMut, Borrow and BorrowMut -use std::fmt::Debug; +use std::{borrow::Borrow, fmt::Debug}; fn main() { // Traits, Description @@ -239,4 +239,42 @@ fn main() { * */ // we have TryFrom and TryInto as equivalent versions of From and Into which can fail and therefore return Result + // + println!("###########################################"); + struct FakeHashMap { + keys: Vec, + values: Vec, + } + + impl FakeHashMap { + fn new() -> Self { + Self { + keys: Vec::new(), + values: Vec::new(), + } + } + + fn insert(&mut self, key: K, value: V) { + self.keys.push(key); + self.values.push(value); + } + + fn get(&self, key: Q) -> Option<&V> + where + K: PartialEq, + Q: Borrow + PartialEq, + { + let index = self.keys.iter().position(|item| item == key.borrow()); + + match index { + Some(idx) => Some(&self.values[idx]), + None => None, + } + } + } + + let mut fmh = FakeHashMap::new(); + fmh.insert("String", "value"); + let value = fmh.get("String"); + println!("Value = {value:?}"); } diff --git a/src/bin/42_closures_indepth.rs b/src/bin/42_closures_indepth.rs new file mode 100644 index 0000000..992af24 --- /dev/null +++ b/src/bin/42_closures_indepth.rs @@ -0,0 +1,212 @@ +use std::{collections::HashMap, thread}; + +fn main() -> std::io::Result<()> { + let mut integers = vec![4, 6, 3, 6, 3, 6, 2, 7, 3, 8, 4, 23, 56]; + integers.sort(); + + println!("Sorted Integers: {integers:?}"); + + #[derive(Default, Debug)] + #[allow(dead_code)] + struct City { + name: String, + capital: String, + population: i64, + } + + impl City { + fn get_statistic(&self, _stat: Statistic) -> i64 { + 10i64 + } + } + + #[derive(Copy, Clone)] + struct Statistic {} + + fn start_sorting_thread( + mut cities: Vec, + stat: Statistic, + ) -> thread::JoinHandle> { + // the move infront of the closure tells the rust compiler that the closure doesn't borrow the values it uses + // but it steals theme + // this first closure takes ownership of stat + let key_fn = move |city: &City| -> i64 { -city.get_statistic(stat) }; + + // the second closure here, takes ownership of cities and the key_fn closure + thread::spawn(move || { + cities.sort_by_key(key_fn); + cities + }) + } + + let cities = vec![ + City { + name: "Uyo".to_string(), + ..Default::default() + }, + City { + name: "Kano".to_string(), + ..Default::default() + }, + ]; + let statistic = Statistic {}; + let result = start_sorting_thread(cities, statistic).join().unwrap(); + + dbg!(result); + // Rust offer 2 ways for closures to take data from the enclosing environement, moves + // + // functions and closures have types just like regular values too + // structs may have function typed fields + // a vector can store functions provided their types are the same + // a function value size is just the size of the machine code since they are pointers + // Note: Closures do not have the same type as functions + + fn calculate(x: i32) -> i32 { + x * 2 + } + + // this is the type of the calculate function above + let _func: fn(i32) -> i32 = calculate; + + // Every Closure has a type that's only known to the compiler + // and no two closures have the same type + // so when working with closures, your code should be generic and filter based on the Fn(T) trait which + // functions and closures implement + // + // closures are very very fast, they are faster than functions pointer + + // FnOnce are closures that kill the values they take and hence must only be called onces + // FnMut are closures that mutate the values they take and are not safe to be passed to threads, this do not kill (drop) the values + // + // Closures are represented as struct that holds references to the values the contain or the values the contain + // based on whether they are borrow closures or move closures + + // Closures can also be Copy or Moves types based on how they used the values the capture + // if everything a borrow closure capture is Copy, the closure is Copy too + // if everything a move closure capture is Copy, the closure is Copy too, this applies for Clone types too + // + let mut greeting = String::from("Hello, "); + let greet = move |name| { + greeting.push_str(name); + println!("{}", greeting); + }; + greet.clone()("Alfred"); // this clones the value in the greeting for each call to clone + greet.clone()("Bruce"); + + // implementation of a router as seen in actix-web to show how closures can be used for callbacks + #[derive(Default)] + #[allow(dead_code)] + struct Request { + method: String, + url: String, + headers: HashMap, + body: Vec, + } + + #[derive(Default)] + #[allow(dead_code)] + struct Response { + code: u32, + headers: HashMap, + body: Vec, + } + + type BoxedCallback = Box Response>; + + struct BasicRouter { + routes: HashMap, + } + + impl BasicRouter { + fn new() -> Self { + Self { + routes: HashMap::new(), + } + } + + fn add_route(mut self, url: &str, callback: C) -> Self + where + C: Fn(&Request) -> Response + 'static, + { + self.routes.insert(url.to_string(), Box::new(callback)); + self + } + + fn not_found_response(&self) -> Response { + Response::default() + } + + fn handle_request(&self, req: &Request) -> Response { + match self.routes.get(&req.url) { + Some(callback) => callback(req), + None => self.not_found_response(), + } + } + } + + struct FnPointerRouter { + routes: HashMap Response>, + } + + impl FnPointerRouter { + fn new() -> Self { + Self { + routes: HashMap::new(), + } + } + + fn add_route(mut self, url: &str, callback: fn(&Request) -> Response) -> Self { + self.routes.insert(url.to_string(), callback); + self + } + + fn not_found_response(&self) -> Response { + Response::default() + } + + fn handle_request(&self, req: &Request) -> Response { + match self.routes.get(&req.url) { + Some(callback) => callback(req), + None => self.not_found_response(), + } + } + } + + fn home(_req: &Request) -> Response { + println!("Running Home Route Handler ✅"); + Response::default() + } + + fn about(_req: &Request) -> Response { + println!("Running About Route Handler ✅"); + Response::default() + } + + let router = BasicRouter::new() + .add_route("/", home) + .add_route("/about", about); + + let fn_pointer_router = FnPointerRouter::new() + .add_route("/", home) + .add_route("/home", home) + .add_route("/about", about); + + println!("Creating Requests"); + let home_request = Request { + url: "/".to_string(), + ..Default::default() + }; + let about_request = Request { + url: "/about".to_string(), + ..Default::default() + }; + + println!("Successfully Created Requests"); + let _response = router.handle_request(&home_request); + let _response = router.handle_request(&about_request); + + let _response = fn_pointer_router.handle_request(&home_request); + let _response = fn_pointer_router.handle_request(&about_request); + + Ok(()) +}