commit e1fe241540b76fbce2066c66478c5bd78fd9cf04
Author: ling0x <ling0x@users.noreply.github.com>
Date: Mon, 8 Jun 2026 03:05:59 +0100
new commit
Diffstat:
84 files changed, 5827 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,3 @@
+node_modules
+public
+target/
+\ No newline at end of file
diff --git a/actors/Cargo.lock b/actors/Cargo.lock
@@ -0,0 +1,218 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "actors"
+version = "0.1.0"
+dependencies = [
+ "tokio",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
+
+[[package]]
+name = "bytes"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.183"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "mio"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
+dependencies = [
+ "errno",
+ "libc",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "socket2"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tokio"
+version = "1.50.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
diff --git a/actors/Cargo.toml b/actors/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "actors"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+tokio = { version = "1.50.0", features = ["full"] }
diff --git a/actors/index.md b/actors/index.md
@@ -0,0 +1,13 @@
+---
+title: "Exercise: Actors"
+tags: [exercise, rust, concurrency, memory-safety]
+---
+
+A from-scratch implementation of an
+[actor](/async_programming/actors.md) in Rust using tokio pmsc
+channels. The goal is to understand how an actor works at the lowest level —
+without relying on any framework following
+[Alice Rhyl's blog post](https://ryhl.io/blog/actors-with-tokio/).
+
+> [!info] Source Code:
+> [exercises/actors/src/main.rs](https://github.com/ling0x/notes/blob/main/content/exercises/actors/src/main.rs)
diff --git a/actors/src/main.rs b/actors/src/main.rs
@@ -0,0 +1,146 @@
+//! An exercise to create a simple actor
+//!
+//! What is an actor?
+//!
+//! The basic idea behind an actor is to spawn a self-contained task that performs
+//! some job independently of other parts of the program.
+//!
+//! Typically these actors communicate with the rest of the program through the
+//! use of message passing channels.
+//!
+//! Since each actor runs independently, programs designed using them are
+//! naturally parallel.
+//!
+//! Things to pay attention to:
+//! 1. Where to put the tokio::spawn call.
+//! 2. Struct with run method vs bare function.
+//! 3. andles to the actor.
+//! 4. Backpressure and bounded channels.
+//! 5. Graceful shutdown.
+
+use tokio::sync::{mpsc, oneshot};
+
+/// An actor is split into two parts: the task and the handle.
+///
+/// The task is the independently spawned Tokio task that actually performs
+/// the duties of the actor, and the handle is a struct that allows you to
+/// communicate with the task.
+struct MyActor {
+ receiver: mpsc::Receiver<ActorMessage>,
+ next_id: u32,
+}
+
+/// The ActorMessage enum defines the kind of messages we can send to the actor.
+///
+/// By using an enum, we can have many different message types, and each message
+/// type can have its own set of arguments. We return a value to the sender by
+/// using an oneshot channel, which is a message passing channel that allows
+/// sending exactly one message.
+enum ActorMessage {
+ GetUniqueId { respond_to: oneshot::Sender<u32> },
+}
+
+impl MyActor {
+ fn new(receiver: mpsc::Receiver<ActorMessage>) -> Self {
+ MyActor {
+ receiver,
+ next_id: 0,
+ }
+ }
+
+ fn handle_message(&mut self, msg: ActorMessage) {
+ // We match on the enum inside a handle_message method on the actor struct,
+ // but that isn't the only way to structure this. One could also match on
+ // the enum in the run_my_actor function. Each branch in this match could
+ // then call various methods such as get_unique_id on the actor object.
+ match msg {
+ ActorMessage::GetUniqueId { respond_to } => {
+ self.next_id += 1;
+
+ // The `let _ =` ignores any errors when sending.
+ //
+ // This can happen if the `select!` macro is used
+ // to cancel waiting for the response.
+ let _ = respond_to.send(self.next_id);
+ }
+ }
+ }
+}
+
+async fn run_my_actor(mut actor: MyActor) {
+ // We can detect when the actor should shut down by looking at failures to
+ // receive messages. In our example, this happens in the following while loop:
+ while let Some(msg) = actor.receiver.recv().await {
+ actor.handle_message(msg);
+ }
+}
+
+/// Now that we have the actor itself, we also need a handle to the actor.
+///
+/// A handle is an object that other pieces of code can use to talk to the actor,
+/// and is also what keeps the actor alive.
+///
+/// Derive Clone: Since the channel allows multiple producers, we can freely
+/// clone our handle to the actor, allowing us to talk to it from multiple places.
+#[derive(Clone)]
+pub struct MyActorHandle {
+ sender: mpsc::Sender<ActorMessage>,
+}
+
+impl MyActorHandle {
+ pub fn new() -> Self {
+ let (sender, receiver) = mpsc::channel(8);
+ let actor = MyActor::new(receiver);
+ tokio::spawn(run_my_actor(actor));
+
+ Self { sender }
+ }
+
+ pub async fn get_unique_id(&self) -> u32 {
+ let (send, recv) = oneshot::channel();
+ let msg = ActorMessage::GetUniqueId { respond_to: send };
+
+ // Ignore send errors. If this send fails, so does the
+ // recv.await below. There's no reason to check for the
+ // same failure twice.
+ let _ = self.sender.send(msg).await;
+ recv.await.expect("Actor task has been killed")
+ }
+}
+
+/// When you call MyActorHandle::new(), it already calls tokio::spawn(run_my_actor(actor))
+/// internally. So by the time new() returns, the actor task is live and waiting
+/// for messages on its mpsc::Receiver.
+///
+/// You never call tokio::spawn in main — it's encapsulated inside MyActorHandle::new(),
+/// which is the idiomatic placement for this pattern. This keeps the spawning
+/// logic close to the actor itself.
+///
+/// Graceful shutdown is automatic — when actor_handle (and all its clones) are
+/// dropped, the mpsc::Sender is dropped, causing actor.receiver.recv().await to
+/// return None, breaking the while let loop and ending the task.
+///
+/// Cloning the handle is safe — MyActorHandle derives Clone, so multiple parts
+/// of your program can send messages to the same actor concurrently without any
+/// extra synchronization, since the actor processes them one at a time.
+///
+/// Backpressure is built in — the channel is bounded (mpsc::channel(8)), so if
+/// the actor can't keep up, senders will .await until there's room, naturally
+/// throttling the workload.
+#[tokio::main]
+async fn main() {
+ // 1. Creating the handle also spawns the actor task automatically (inside MyActorHandle::new)
+ let actor_handle = MyActorHandle::new();
+
+ // 2. Send a message to the actor and await the response
+ let id1 = actor_handle.get_unique_id().await;
+ println!("Got id: {}", id1); // prints 1
+
+ let id2 = actor_handle.get_unique_id().await;
+ println!("Got id: {}", id2); // prints 2
+
+ // 3. Clone the handle to show multiple owners can talk to the same actor
+ let handle2 = actor_handle.clone();
+ let id3 = handle2.get_unique_id().await;
+ println!("Got id from cloned handle: {}", id3); // prints 3
+}
diff --git a/channels/Cargo.lock b/channels/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "channels"
+version = "0.1.0"
diff --git a/channels/Cargo.toml b/channels/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "channels"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
diff --git a/channels/src/main.rs b/channels/src/main.rs
@@ -0,0 +1,3 @@
+fn main() {
+ println!("Hello, world!");
+}
diff --git a/design_patterns_in_rust/bad_calculator/Cargo.lock b/design_patterns_in_rust/bad_calculator/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "bad_calculator"
+version = "0.1.0"
diff --git a/design_patterns_in_rust/bad_calculator/Cargo.toml b/design_patterns_in_rust/bad_calculator/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bad_calculator"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
diff --git a/design_patterns_in_rust/bad_calculator/src/main.rs b/design_patterns_in_rust/bad_calculator/src/main.rs
@@ -0,0 +1,167 @@
+//! This is a bad example of trying to apply OO principles in rust
+//! and make it worse
+
+use std::{
+ io::{Write, stdin, stdout},
+ ops::{Deref, DerefMut},
+ process::exit,
+};
+
+trait Operand {
+ fn evaluate(&self) -> f64;
+}
+
+/// There are a lot of box dyn types showing up, which is bit
+/// concerning, but lets continue to make the bad calculator
+trait Operator {
+ fn precedence(&self) -> u8;
+ fn symbol(&self) -> char;
+ fn push_operand(&mut self, operand: Box<dyn Operand>);
+ fn pop_operand(&mut self) -> Box<dyn Operand>;
+ fn apply(&mut self) -> Box<dyn Operand>;
+}
+
+trait UnaryOperator: crate::Operator {
+ fn apply_unary(&self, operand: Box<dyn Operand>) -> Box<dyn Operand>;
+
+ fn apply(&mut self) -> Box<dyn Operand> {
+ let operand = self.pop_operand();
+ self.apply_unary(operand)
+ }
+}
+
+trait BinaryOperator: Operator {
+ fn apply_binary(
+ &self,
+ operand1: Box<dyn Operand>,
+ operand2: Box<dyn Operand>,
+ ) -> Box<dyn Operand>;
+
+ fn apply(&mut self) -> Box<dyn Operand> {
+ let operand2 = self.pop_operand();
+ let operand1 = self.pop_operand();
+ self.apply_binary(operand1, operand2)
+ }
+}
+
+struct OperandStack(Vec<Box<dyn Operand>>);
+
+impl OperandStack {
+ fn new() -> Self {
+ Self(Vec::new())
+ }
+
+ fn push_operand(&mut self, operand: Box<dyn Operand>) {
+ self.0.push(operand);
+ }
+
+ fn pop_operand(&mut self) -> Box<dyn Operand> {
+ self.0.pop().unwrap()
+ }
+
+ fn clear_stack(&mut self) {
+ self.0.clear();
+ }
+}
+
+struct AdditionOperator {
+ stack: OperandStack,
+}
+
+impl AdditionOperator {
+ fn new() -> Self {
+ Self {
+ stack: OperandStack::new(),
+ }
+ }
+}
+
+impl BinaryOperator for AdditionOperator {
+ fn apply_binary(
+ &self,
+ operand1: Box<dyn Operand>,
+ operand2: Box<dyn Operand>,
+ ) -> Box<dyn Operand> {
+ let inner_operand2 = operand2.as_ref().evaluate();
+ let inner_operand1 = operand1.evaluate();
+ let result = inner_operand1 + inner_operand2;
+ Box::new(Value(result))
+ }
+}
+
+impl Operator for AdditionOperator {
+ fn precedence(&self) -> u8 {
+ 0
+ }
+
+ fn symbol(&self) -> char {
+ '+'
+ }
+
+ fn push_operand(&mut self, operand: Box<dyn Operand>) {
+ self.stack.push_operand(operand);
+ }
+
+ fn pop_operand(&mut self) -> Box<dyn Operand> {
+ self.stack.pop_operand()
+ }
+
+ fn apply(&mut self) -> Box<dyn Operand> {
+ let operand2 = self.pop_operand();
+ let operand1 = self.pop_operand();
+ self.apply_binary(operand1, operand2)
+ }
+}
+
+impl Deref for AdditionOperator {
+ type Target = OperandStack;
+
+ fn deref(&self) -> &Self::Target {
+ &self.stack
+ }
+}
+
+impl DerefMut for AdditionOperator {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.stack
+ }
+}
+
+struct Value(f64);
+
+impl Operand for Value {
+ fn evaluate(&self) -> f64 {
+ self.0
+ }
+}
+
+fn evaluate_expression(expression: &str) -> Result<String, String> {
+ let some_operand = Box::new(Value(0.0));
+ let mut addition_operator = AdditionOperator::new();
+ addition_operator.push_operand(some_operand);
+ addition_operator.clear_stack();
+ let popped_operand = addition_operator.pop_operand();
+ Ok("Finished".to_string())
+}
+
+/// This is a project to demonstrate BAD practices in Rust in order to learn
+fn main() {
+ let mut buf = String::new();
+ loop {
+ print!("> ");
+
+ stdout().flush().unwrap();
+
+ buf.clear();
+ stdin().read_line(&mut buf).unwrap();
+
+ if buf.trim() == "exit" {
+ exit(0)
+ }
+
+ match evaluate_expression(&buf) {
+ Ok(result) => println!("{result}"),
+ Err(error) => println!("Error: {error}"),
+ }
+ }
+}
diff --git a/design_patterns_in_rust/bad_calculator_2/Cargo.lock b/design_patterns_in_rust/bad_calculator_2/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "bad_calculator_2"
+version = "0.1.0"
diff --git a/design_patterns_in_rust/bad_calculator_2/Cargo.toml b/design_patterns_in_rust/bad_calculator_2/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bad_calculator_2"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
diff --git a/design_patterns_in_rust/bad_calculator_2/src/cloning_bad_example.rs b/design_patterns_in_rust/bad_calculator_2/src/cloning_bad_example.rs
@@ -0,0 +1,71 @@
+//! This code is a bad example that is riddled with unecessary cloning
+
+use std::collections::HashMap;
+
+/// Notice `#[derive(Clone)]` on both structs, this is our first warning.
+/// Do we really need to derive it on everything?
+#[derive(Clone)]
+struct Variable {
+ name: String,
+ value: f64,
+}
+
+/// Another derive Clone :/
+#[derive(Clone)]
+enum Token {
+ Number(f64),
+ Variable(Variable),
+ Operator(char),
+}
+
+struct Calculator {
+ variables: HashMap<String, Variable>,
+}
+
+/// Bad practice: use clone() everywhere...
+impl Calculator {
+ fn tokenize(&self, expression: &str) -> Vec<Token> {
+ let mut tokens = Vec::new();
+
+ for part in expression.split_whitespace() {
+ if let Some(var) = self.variables.get(part) {
+ // Another clone() :]
+ tokens.push(Token::Variable(var.clone()));
+ }
+ // ... rest of tokenization
+ }
+ tokens
+ }
+
+ fn apply_operator(&self, left: Token, right: Token) -> f64 {
+ todo!()
+ }
+
+ fn evaluate(&self, tokens: Vec<Token>) -> f64 {
+ // Yet another clone...
+ let mut working_tokens = tokens.clone();
+
+ while working_tokens.len() > 1 {
+ // Find next operator
+ let op_pos = working_tokens
+ .iter()
+ .position(|t| matches!(t, Token::Operator(_)))
+ .unwrap();
+
+ // So many clones :/
+ let left = working_tokens[op_pos - 1].clone();
+ let right = working_tokens[op_pos + 1].clone();
+
+ let result = self.apply_operator(left, right);
+
+ // Remove old tokens and insert result
+ working_tokens.drain(op_pos - 1..=op_pos + 1);
+ working_tokens.insert(op_pos - 1, Token::Number(result));
+ }
+
+ match working_tokens[0] {
+ Token::Number(n) => n,
+ _ => panic!("Invalid expression"),
+ }
+ }
+}
diff --git a/design_patterns_in_rust/bad_calculator_2/src/cloning_better_example.rs b/design_patterns_in_rust/bad_calculator_2/src/cloning_better_example.rs
@@ -0,0 +1,65 @@
+use std::collections::HashMap;
+
+struct Variable {
+ name: String,
+ value: f64,
+}
+
+/// Notice that Token now holds a reference to Variable instead of owning a clone
+/// The 'a lifetime parameter ties the token's validity to the varaible it references
+enum Token<'a> {
+ Number(f64),
+ Variable(&'a Variable),
+ Operator(char),
+}
+
+struct Calculator {
+ variables: HashMap<String, Variable>,
+}
+
+impl Calculator {
+ /// The methods now work with references instead of clones
+ fn tokenize<'a>(&'a self, expression: &str) -> Vec<Token<'a>> {
+ let mut tokens = Vec::new();
+
+ for part in expression.split_whitespace() {
+ if let Some(var) = self.variables.get(part) {
+ // No need to clone, just use a reference
+ tokens.push(Token::Variable(var));
+ }
+ // ... rest of tokenization
+ }
+ tokens
+ }
+
+ fn apply_operator(&self, left: f64, right: f64) -> f64 {
+ todo!()
+ }
+
+ fn evaluate(&self, mut tokens: Vec<Token>) -> f64 {
+ while tokens.len() > 1 {
+ // Find next operator
+ let op_pos = tokens
+ .iter()
+ .position(|t| matches!(t, Token::Operator(_)))
+ .unwrap();
+
+ // Calculate the result using references
+ let result = match (&tokens[op_pos - 1], &tokens[op_pos + 1]) {
+ (Token::Number(n1), Token::Number(n2)) => self.apply_operator(*n1, *n2),
+ (Token::Variable(v1), Token::Number(n2)) => self.apply_operator(v1.value, *n2),
+ // ... other combinations
+ _ => panic!("Invalid expression"),
+ };
+
+ // Remove old tokens and insert result
+ tokens.drain(op_pos - 1..=op_pos + 1);
+ tokens.insert(op_pos - 1, Token::Number(result));
+ }
+
+ match tokens[0] {
+ Token::Number(n) => n,
+ _ => panic!("Invalid expression"),
+ }
+ }
+}
diff --git a/design_patterns_in_rust/bad_calculator_2/src/lib.rs b/design_patterns_in_rust/bad_calculator_2/src/lib.rs
@@ -0,0 +1,19 @@
+/// Example 1: fighting the borrow check with refcell
+pub mod ownership_bad_example;
+
+/// Example 2: make it work within the ownership rules
+pub mod ownership_better_example;
+
+/// Example 3: cloning - using clone() everywhere as a clone hammer
+pub mod cloning_bad_example;
+
+/// Example 4: cloning - work within the ownership and lifetime rules
+pub mod cloning_better_example;
+
+/// Example 5: smart pointers - over-engineer with smart pointers and wrap
+/// everything in Rc<RefCell<...>>
+pub mod smart_pointers_bad_example;
+
+/// Example 6: smart pointers - design a system by thinking about ownership
+/// and data flow upfront
+pub mod smart_pointers_better_example;
diff --git a/design_patterns_in_rust/bad_calculator_2/src/main.rs b/design_patterns_in_rust/bad_calculator_2/src/main.rs
@@ -0,0 +1,27 @@
+use std::{fmt::Pointer, thread::spawn};
+
+use bad_calculator_2::smart_pointers_better_example::{Calculator, ThreadSafeCalculator};
+
+fn main() -> Result<(), String> {
+ let mut calc = Calculator::new();
+
+ calc.set_variable("pi".to_string(), 3.14159);
+
+ let result1 = calc.evaluate("2 * pi".to_string())?;
+ let result2 = calc.evaluate("result + 1".to_string())?;
+
+ for calculation in calc.history() {
+ println!("{} = {}", calculation.expression, calculation.result);
+ }
+
+ let thread_safe = ThreadSafeCalculator::new();
+
+ let threads: Vec<_> = (0..3)
+ .map(|i| {
+ let calc = thread_safe.clone();
+ spawn(move || calc.evaluate(format!("{} + 1", i)))
+ })
+ .collect();
+
+ Ok(())
+}
diff --git a/design_patterns_in_rust/bad_calculator_2/src/ownership_bad_example.rs b/design_patterns_in_rust/bad_calculator_2/src/ownership_bad_example.rs
@@ -0,0 +1,65 @@
+use std::cell::{Ref, RefCell};
+
+struct CalculationResult {
+ expression: String,
+ result: f64,
+}
+
+/// Anti-pattern: trying to circumvent the borrow checker with RefCell
+struct Calculator {
+ history: RefCell<Vec<CalculationResult>>,
+ current_expression: RefCell<Option<String>>,
+}
+
+trait HistoryViewer {
+ fn view_history(&self) -> Ref<Vec<CalculationResult>>;
+ fn get_last_result(&self) -> Option<f64>;
+}
+
+trait HistoryManager {
+ fn add_to_history(&self, expression: String, result: f64);
+ fn clear_history(&self);
+}
+
+impl Calculator {
+ fn new() -> Self {
+ Self {
+ history: RefCell::new(Vec::new()),
+ current_expression: RefCell::new(None),
+ }
+ }
+}
+
+/// Implementing HistoryViewer works fine, as we can reference the data with &self
+impl HistoryViewer for Calculator {
+ fn view_history(&self) -> Ref<Vec<CalculationResult>> {
+ self.history.borrow()
+ }
+
+ fn get_last_result(&self) -> Option<f64> {
+ self.history.borrow().last().map(|r| r.result)
+ }
+}
+
+/// However, mutable access is more problematic
+///
+/// One bad practice is to use RefCell to make the compiler "happy"
+/// using `.borrow_mut()`
+///
+/// Even though our code compiles, we've introduced other problems.
+/// Rather than using compile-time guarantees, we are now relying on runtime
+/// borrow checking, which can potentially panic.
+///
+/// The code is also more difficult to reason about and work with, because
+/// we are now effectively hiding mutation behind shared references.
+impl HistoryManager for Calculator {
+ fn add_to_history(&self, expression: String, result: f64) {
+ self.history
+ .borrow_mut()
+ .push(CalculationResult { expression, result });
+ }
+
+ fn clear_history(&self) {
+ self.history.borrow_mut().clear();
+ }
+}
diff --git a/design_patterns_in_rust/bad_calculator_2/src/ownership_better_example.rs b/design_patterns_in_rust/bad_calculator_2/src/ownership_better_example.rs
@@ -0,0 +1,56 @@
+//! Better calculator: work within ownership rules
+//!
+//! The code below now avoids runtime checking overhead as in Rc and RefCell,
+//! and it's easier to reason about.
+
+struct CalculationResult {
+ expression: String,
+ result: f64,
+}
+
+struct Calculator {
+ history: Vec<CalculationResult>,
+ current_expression: Option<String>,
+}
+
+/// If we need to share access to history, we can create a dedicated type
+///
+/// A clean view-only type that introduces lifetimes, which are Rust's way of
+/// tracking how long references remain valid.
+struct HistoryView<'a> {
+ entries: &'a [CalculationResult],
+}
+
+/// It is clear when mutation can occur because of the method signatures
+impl Calculator {
+ fn new() -> Self {
+ Self {
+ history: Vec::new(),
+ current_expression: None,
+ }
+ }
+
+ fn create_history_view(&self) -> HistoryView<'_> {
+ HistoryView {
+ entries: &self.history,
+ }
+ }
+
+ fn add_to_history(&mut self, expression: String, result: f64) {
+ self.history.push(CalculationResult { expression, result });
+ }
+
+ fn clear_history(&mut self) {
+ self.history.clear();
+ }
+
+ fn calculate_expression(&self, expression: &str) -> Result<f64, String> {
+ todo!()
+ }
+
+ fn evaluate(&mut self, expression: String) -> Result<f64, String> {
+ let result = self.calculate_expression(&expression)?;
+ self.add_to_history(expression, result);
+ Ok(result)
+ }
+}
diff --git a/design_patterns_in_rust/bad_calculator_2/src/smart_pointers_bad_example.rs b/design_patterns_in_rust/bad_calculator_2/src/smart_pointers_bad_example.rs
@@ -0,0 +1,78 @@
+//! Bad practice: over-engieer with smart pointers and wrap everything in
+//! Rc<RefCell<_>>, which resulting in the code become cluttered with
+//! borrow() and borrow_mut() calls.
+
+use std::{
+ cell::{Ref, RefCell},
+ collections::HashMap,
+ rc::Rc,
+};
+
+struct Variable {
+ name: String,
+ value: f64,
+}
+
+enum Token {
+ Number(f64),
+ Variable(Variable),
+ Operator(char),
+}
+
+struct Expression {
+ tokens: Rc<RefCell<Vec<Token>>>,
+ result: Rc<RefCell<Option<f64>>>,
+}
+
+struct Calculator {
+ current_expression: Rc<RefCell<Option<Expression>>>,
+ variables: Rc<RefCell<HashMap<String, f64>>>,
+}
+
+/// This code has several serious issues.
+/// 1. it is verbose and difficult to reason about
+/// 2. we have to manually manage lifetimes
+/// 3. there is an inherent cost to reference counting
+impl Calculator {
+ fn new() -> Self {
+ Self {
+ current_expression: Rc::new(RefCell::new(None)),
+ variables: Rc::new(RefCell::new(HashMap::new())),
+ }
+ }
+
+ fn tokenize(&self, expr: &str) -> Vec<Token> {
+ todo!()
+ }
+
+ fn set_expression(&self, expr: &str) {
+ let tokens = self.tokenize(expr);
+
+ *self.current_expression.borrow_mut() = Some(Expression {
+ tokens: Rc::new(RefCell::new(tokens)),
+ result: Rc::new(RefCell::new(None)),
+ })
+ }
+
+ fn process_tokens(
+ &self,
+ tokens: &mut Vec<Token>,
+ vars: &HashMap<String, f64>,
+ ) -> Result<f64, String> {
+ todo!()
+ }
+
+ fn evaluate(&self) -> Result<f64, String> {
+ let expr = self.current_expression.borrow();
+ let expr = expr.as_ref().ok_or("No expression set")?;
+
+ let mut tokens = expr.tokens.borrow_mut();
+ let vars = self.variables.borrow();
+
+ // Process tokens...
+ let result = self.process_tokens(&mut tokens, &vars)?;
+
+ *expr.result.borrow_mut() = Some(result);
+ Ok(result)
+ }
+}
diff --git a/design_patterns_in_rust/bad_calculator_2/src/smart_pointers_better_example.rs b/design_patterns_in_rust/bad_calculator_2/src/smart_pointers_better_example.rs
@@ -0,0 +1,119 @@
+//! Better design without misusing smart points:
+//! Data flow in one direction
+//! Each step produces output the next step consumes
+//! Calculator owns its variables and history directly, so no shared ownership
+//! is needed
+
+use std::{
+ collections::HashMap,
+ sync::{Arc, Mutex},
+};
+
+#[derive(Clone)]
+enum Token {
+ Number(f64),
+ Variable(String),
+ Operator(char),
+}
+
+struct ParsedExpression {
+ tokens: Vec<Token>,
+}
+
+pub struct Calculation {
+ pub expression: String,
+ tokens: Vec<Token>,
+ pub result: f64,
+}
+
+pub struct Calculator {
+ variables: HashMap<String, f64>,
+ history: Vec<Calculation>,
+}
+
+/// In each methods, we ask for the kind of reference we need, mutable or immutable,
+/// and then have clean compiler verified access to the data
+impl Calculator {
+ pub fn new() -> Self {
+ Self {
+ variables: HashMap::new(),
+ history: Vec::new(),
+ }
+ }
+
+ fn tokenize(&self, expr: &str) -> Vec<Token> {
+ todo!()
+ }
+
+ fn parse(&self, expr: &str) -> Result<ParsedExpression, String> {
+ let tokens = self.tokenize(expr);
+ Ok(ParsedExpression { tokens })
+ }
+
+ fn evaluate_tokens(&self, tokens: Vec<Token>) -> Result<f64, String> {
+ todo!()
+ }
+
+ fn evaluate_parsed(&mut self, expr: String, parsed: ParsedExpression) -> Result<f64, String> {
+ let result = self.evaluate_tokens(parsed.tokens.clone())?;
+
+ self.history.push(Calculation {
+ expression: expr,
+ tokens: parsed.tokens,
+ result,
+ });
+
+ Ok(result)
+ }
+
+ // With unambiguous access patterns, we can create convenient methods that
+ // follows this pattern
+ pub fn evaluate(&mut self, expr: String) -> Result<f64, String> {
+ let parsed = self.parse(&expr)?;
+ self.evaluate_parsed(expr, parsed)
+ }
+
+ pub fn history(&self) -> &[Calculation] {
+ &self.history
+ }
+
+ fn last_result(&self) -> Option<f64> {
+ self.history.last().map(|calc| calc.result)
+ }
+
+ pub fn set_variable(&mut self, name: String, value: f64) {
+ self.variables.insert(name, value);
+ }
+
+ fn get_variables(&self, name: &str) -> Option<f64> {
+ self.variables.get(name).copied()
+ }
+}
+
+/// When we really need thread-safe access, we add it through a dedicated wrapper
+pub struct ThreadSafeCalculator {
+ inner: Arc<Mutex<Calculator>>,
+}
+
+impl ThreadSafeCalculator {
+ pub fn new() -> Self {
+ Self {
+ inner: Arc::new(Mutex::new(Calculator::new())),
+ }
+ }
+
+ pub fn evaluate(&self, expr: String) -> Result<f64, String> {
+ let mut calc = self.inner.lock().map_err(|_| "Lock poinsoned")?;
+ calc.evaluate(expr)
+ }
+
+ // Other methods following the same pattern...
+}
+
+impl Clone for ThreadSafeCalculator {
+ fn clone(&self) -> Self {
+ Self {
+ inner: Arc::clone(&self.inner),
+ }
+ }
+}
diff --git a/design_patterns_in_rust/bad_calculator_3/Cargo.lock b/design_patterns_in_rust/bad_calculator_3/Cargo.lock
@@ -0,0 +1,16 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "bad_calculator_3"
+version = "0.1.0"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
diff --git a/design_patterns_in_rust/bad_calculator_3/Cargo.toml b/design_patterns_in_rust/bad_calculator_3/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "bad_calculator_3"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+lazy_static = "1.5.0"
diff --git a/design_patterns_in_rust/bad_calculator_3/src/bad_example.rs b/design_patterns_in_rust/bad_calculator_3/src/bad_example.rs
@@ -0,0 +1,78 @@
+//! This is a bad example demonstrating how circumventing
+//! the borrow checker is a bad practice in rust
+
+use std::cell::RefCell;
+
+#[derive(Debug)]
+enum Token {
+ Number(f64),
+ ResultReference(usize),
+ Operr(char),
+}
+
+/// The struct stores both the current value and a history
+/// of all previous results in a vec
+struct Calculator {
+ current_value: f64,
+ memory: RefCell<Vec<f64>>,
+}
+
+impl Calculator {
+ fn new() -> Self {
+ Self {
+ current_value: 0.0,
+ memory: RefCell::new(Vec::new()),
+ }
+ }
+
+ /// We either retrive a previous result or compute a
+ /// new one, then update the calculator's state
+ fn evaluate(&mut self, expression: &str) -> Result<f64, String> {
+ if expression.starts_with("result") {
+ if let Some(index) = expression.strip_prefix("result") {
+ if let Ok(offset) = index.trim().parse::<usize>() {
+ return self.get_previous_result(offset);
+ }
+ }
+ }
+
+ let result = self.parse_and_evaluate(expression)?;
+
+ self.memory.borrow_mut().push(result);
+ self.current_value = result;
+
+ Ok(result)
+ }
+
+ fn get_previous_result(&self, index: usize) -> Result<f64, String> {
+ if index == 0 {
+ Ok(self.current_value)
+ } else {
+ let memory = self.memory.borrow();
+ let pos = memory
+ .len()
+ .checked_sub(index)
+ .ok_or("Invalid result index")?;
+ memory
+ .get(pos)
+ .copied()
+ .ok_or_else(|| "Invalid result index".to_string())
+ }
+ }
+
+ fn parse_and_evaluate(&self, expression: &str) -> Result<f64, String> {
+ let tokens = self.tokenize(expression)?;
+
+ for token in &tokens {
+ if let Token::ResultReference(index) = token {
+ let prev = self.get_previous_result(*index)?;
+ }
+ }
+
+ todo!("Actual evaluation TBD")
+ }
+
+ fn tokenize(&self, expression: &str) -> Result<Vec<Token>, String> {
+ todo!()
+ }
+}
diff --git a/design_patterns_in_rust/bad_calculator_3/src/bad_example_statics.rs b/design_patterns_in_rust/bad_calculator_3/src/bad_example_statics.rs
@@ -0,0 +1,122 @@
+use std::{collections::HashMap, sync::Mutex};
+
+use lazy_static::lazy_static;
+
+lazy_static! {
+ static ref MEMORY: Mutex<Vec<f64>> = Mutex::new(Vec::new());
+ static ref VARIABLES: Mutex<HashMap<String, f64>> = Mutex::new(HashMap::new());
+}
+
+#[derive(Debug)]
+enum Token {
+ Number(f64),
+ Variable(String),
+ Operator(char),
+}
+
+struct Calculator;
+
+impl Calculator {
+ fn new() -> Self {
+ Self
+ }
+
+ fn store_result(&self, result: f64) {
+ let mut memory = MEMORY.lock().unwrap();
+ memory.push(result);
+ }
+
+ fn get_previous_result(&self, index: usize) -> Option<f64> {
+ let memory = MEMORY.lock().unwrap();
+ if index == 0 {
+ memory.last().copied()
+ } else {
+ let pos = memory.len().checked_sub(index)?;
+ memory.get(pos).copied()
+ }
+ }
+
+ fn tokenize(&self, expression: &str) -> Result<Vec<Token>, String> {
+ todo!()
+ }
+
+ fn evaluate(&self, expression: &str) -> Result<f64, String> {
+ if let Some((name, value_expr)) = expression.split_once('=') {
+ let value = self.evaluate(value_expr.trim())?;
+ let mut vars = VARIABLES.lock().unwrap();
+ vars.insert(name.trim().to_string(), value);
+ return Ok(value);
+ }
+
+ let result = self.parse_and_evaluate(expression)?;
+ self.store_result(result);
+ Ok(result)
+ }
+
+ fn evaluate_tokens(&self, tokens: Vec<Token>) -> Result<f64, String> {
+ todo!()
+ }
+
+ fn parse_and_evaluate(&self, expression: &str) -> Result<f64, String> {
+ let tokens = self.tokenize(expression)?;
+
+ let mut resolved_tokens = Vec::new();
+
+ for token in tokens {
+ match token {
+ Token::Variable(name) => {
+ let vars = VARIABLES.lock().unwrap();
+ let value = vars
+ .get(&name)
+ .ok_or_else(|| format!("Undefined variable: {}", name))?;
+ resolved_tokens.push(Token::Number(*value));
+ }
+ token => resolved_tokens.push(token),
+ }
+ }
+
+ self.evaluate_tokens(resolved_tokens)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn clear_memory() {
+ let mut memory = MEMORY.lock().unwrap();
+ memory.clear();
+ }
+
+ fn clear_variables() {
+ let mut variables = VARIABLES.lock().unwrap();
+ variables.clear();
+ }
+
+ #[test]
+ fn test_calculator_results() -> Result<(), String> {
+ clear_memory();
+ clear_variables();
+
+ let calc = Calculator::new();
+ calc.evaluate("x = 5")?;
+ calc.evaluate("y = x + 3")?;
+
+ assert_eq!(calc.evaluate("y")?, 8.0);
+ Ok(())
+ }
+
+ #[test]
+ fn test_interdependent_variables() -> Result<(), String> {
+ clear_memory();
+ clear_variables();
+
+ let calc = Calculator::new();
+
+ calc.evaluate("a = 1")?;
+ calc.evaluate("b = a + 1")?;
+ calc.evaluate("a = b + 1")?;
+ assert_eq!(calc.evaluate("a")?, 3.0);
+ Ok(())
+ }
+}
diff --git a/design_patterns_in_rust/bad_calculator_3/src/bad_example_unsafe.rs b/design_patterns_in_rust/bad_calculator_3/src/bad_example_unsafe.rs
@@ -0,0 +1,108 @@
+/// It stores the expression string and the computed result
+struct CalculatorState {
+ expression: String,
+ result: f64,
+}
+
+/// It stores states in a Vec and maintains a raw pointer to the
+/// current state
+pub struct UnsafeHistory {
+ states: Vec<CalculatorState>,
+ current: Option<*const CalculatorState>,
+}
+
+impl UnsafeHistory {
+ fn new() -> Self {
+ Self {
+ states: Vec::with_capacity(10),
+ current: None,
+ }
+ }
+
+ fn push(&mut self, state: CalculatorState) {
+ self.states.push(state);
+ self.current = Some(self.states.last().unwrap() as *const CalculatorState);
+ }
+
+ /// This method dereferences our raw pointer inside an unsafe
+ /// block to retrieve the result
+ /// This is like telling the compiler: "Trust me, this pointer
+ /// is valid"...
+ pub fn current_result(&self) -> Option<f64> {
+ self.current.map(|ptr| unsafe { (*ptr).result })
+ }
+
+ /// This method finds our current position in the Vec, then moves
+ /// the current pointer back one slot and returns that result
+ fn undo(&mut self) -> Option<f64> {
+ let ptr = self.current?;
+
+ // Find current position and move back one
+ let pos = self
+ .states
+ .iter()
+ .position(|state| std::ptr::eq(state, ptr))?;
+
+ if pos > 0 {
+ self.current = Some(&self.states[pos - 1] as *const CalculatorState);
+ self.current_result()
+ } else {
+ None // Already at the beginning
+ }
+ }
+
+ /// Move forward in history, then dereference the raw pointer
+ /// to get its state
+ fn redo(&mut self) -> Option<f64> {
+ let ptr = self.current?;
+ let pos = self.states.iter().position(|s| std::ptr::eq(s, ptr))?;
+
+ if pos + 1 < self.states.len() {
+ self.current = Some(&self.states[pos + 1] as *const CalculatorState);
+ self.current_result()
+ } else {
+ None // Already at the end
+ }
+ }
+}
+
+pub struct Calculator {
+ pub history: UnsafeHistory,
+}
+
+impl Calculator {
+ pub fn new() -> Self {
+ Self {
+ history: UnsafeHistory::new(),
+ }
+ }
+
+ pub fn evaluate(&mut self, expression: &str) -> Result<f64, String> {
+ if expression == "undo" {
+ return self
+ .history
+ .undo()
+ .ok_or_else(|| "Nothing to undo".to_string());
+ }
+
+ if expression == "redo" {
+ return self
+ .history
+ .redo()
+ .ok_or_else(|| "Nothing to redo".to_string());
+ }
+
+ let result = self.parse_and_evaluate(expression)?;
+
+ self.history.push(CalculatorState {
+ expression: expression.to_string(),
+ result,
+ });
+
+ Ok(result)
+ }
+
+ fn parse_and_evaluate(&self, expression: &str) -> Result<f64, String> {
+ todo!("Evaluation TBD")
+ }
+}
diff --git a/design_patterns_in_rust/bad_calculator_3/src/better_example.rs b/design_patterns_in_rust/bad_calculator_3/src/better_example.rs
@@ -0,0 +1,70 @@
+#[derive(Debug)]
+enum Token {
+ Number(f64),
+ ResultReference(usize),
+ Operator(char),
+}
+
+/// The struct stores both the current value and a history
+/// of all previous results in a vec
+struct Calculator {
+ current_value: f64,
+ memory: Vec<f64>,
+}
+
+impl Calculator {
+ fn tokenize(&self, expression: &str) -> Result<Vec<Token>, String> {
+ let mut tokens = Vec::new();
+
+ for part in expression.split_whitespace() {
+ let token = if let Some(index) = part.strip_prefix("result") {
+ if let Ok(offset) = index.trim().parse() {
+ Token::ResultReference(offset)
+ } else {
+ return Err("Invalid result reference".to_string());
+ }
+ } else if let Ok(num) = part.parse() {
+ Token::Number(num)
+ } else if part.len() == 1 && "+-*/".contains(part) {
+ Token::Operator(part.chars().next().unwrap())
+ } else {
+ return Err(format!("Invalid token: {}", part));
+ };
+
+ tokens.push(token);
+ }
+
+ Ok(tokens)
+ }
+
+ fn evaluate_tokens(&self, tokens: Vec<Token>) -> Result<f64, String> {
+ todo!("The evaluator is TBD")
+ }
+
+ fn evaluate(&mut self, expression: &str) -> Result<f64, String> {
+ let tokens = self.tokenize(expression)?;
+
+ let mut resolved_tokens = Vec::new();
+
+ for token in tokens {
+ match token {
+ Token::ResultReference(index) => {
+ let value = self.get_previous_result(index)?;
+ resolved_tokens.push(Token::Number(value));
+ }
+ token => resolved_tokens.push(token),
+ }
+ }
+
+ let result = self.evaluate_tokens(resolved_tokens)?;
+
+ self.memory.push(result);
+ self.current_value = result;
+
+ Ok(result)
+ }
+
+ fn get_previous_result(&self, index: usize) -> Result<f64, String> {
+ todo!()
+ }
+}
diff --git a/design_patterns_in_rust/bad_calculator_3/src/better_example_no_statics.rs b/design_patterns_in_rust/bad_calculator_3/src/better_example_no_statics.rs
@@ -0,0 +1,89 @@
+use std::collections::HashMap;
+
+#[derive(Debug)]
+enum Token {
+ Number(f64),
+ Variable(String),
+ Operator(char),
+}
+
+struct Calculator {
+ memory: Vec<f64>,
+ variables: HashMap<String, f64>,
+}
+
+impl Calculator {
+ fn new() -> Self {
+ Self {
+ memory: Vec::new(),
+ variables: HashMap::new(),
+ }
+ }
+
+ fn tokenize(&self, expression: &str) -> Result<Vec<Token>, String> {
+ todo!()
+ }
+
+ fn evaluate(&mut self, expression: &str) -> Result<f64, String> {
+ if let Some((name, value_expr)) = expression.split_once('=') {
+ let value = self.evaluate(value_expr.trim())?;
+ self.variables.insert(name.trim().to_string(), value);
+ return Ok(value);
+ }
+
+ let tokens = self.tokenize(expression)?;
+ let resolved_tokens = self.resolve_variables(tokens)?;
+ let result = self.evaluate_tokens(resolved_tokens)?;
+
+ self.memory.push(result);
+ Ok(result)
+ }
+
+ fn evaluate_tokens(&self, tokens: Vec<Token>) -> Result<f64, String> {
+ todo!()
+ }
+
+ fn resolve_variables(&self, tokens: Vec<Token>) -> Result<Vec<Token>, String> {
+ tokens
+ .into_iter()
+ .map(|token| match token {
+ Token::Variable(name) => {
+ let value = self
+ .variables
+ .get(&name)
+ .ok_or_else(|| format!("Undefined variable: {}", name))?;
+ Ok(Token::Number(*value))
+ }
+
+ token => Ok(token),
+ })
+ .collect()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_calculator() -> Result<(), String> {
+ let mut calc = Calculator::new();
+
+ calc.evaluate("x = 5")?;
+ calc.evaluate("y = x + 3")?;
+
+ assert_eq!(calc.evaluate("y")?, 8.0);
+ Ok(())
+ }
+
+ #[test]
+ fn test_interlocking_variables() -> Result<(), String> {
+ let mut calc = Calculator::new();
+
+ calc.evaluate("a = 1")?;
+ calc.evaluate("b = a + 1")?;
+ calc.evaluate("a = b + 1")?;
+ assert_eq!(calc.evaluate("a")?, 3.0);
+ Ok(())
+ }
+}
diff --git a/design_patterns_in_rust/bad_calculator_3/src/better_example_no_unsafe.rs b/design_patterns_in_rust/bad_calculator_3/src/better_example_no_unsafe.rs
@@ -0,0 +1,94 @@
+/// It stores the expression string and the computed result
+struct CalculatorState {
+ expression: String,
+ result: f64,
+}
+
+/// It stores states in a Vec and maintains a position index to the
+/// current state
+pub struct History {
+ states: Vec<CalculatorState>,
+ position: usize,
+}
+
+impl History {
+ fn new() -> Self {
+ Self {
+ states: Vec::with_capacity(10),
+ position: 0,
+ }
+ }
+
+ fn push(&mut self, state: CalculatorState) {
+ // When pushing after an undo, discard the "future" states
+ self.states.truncate(self.position);
+ self.states.push(state);
+ self.position = self.states.len();
+ }
+
+ pub fn current_result(&self) -> Option<f64> {
+ if self.position > 0 {
+ self.states.get(self.position - 1).map(|state| state.result)
+ } else {
+ None
+ }
+ }
+
+ fn undo(&mut self) -> Option<f64> {
+ if self.position > 0 {
+ self.states.get(self.position - 1).map(|state| state.result)
+ } else {
+ None
+ }
+ }
+
+ fn redo(&mut self) -> Option<f64> {
+ if self.position < self.states.len() {
+ self.position += 1;
+ self.current_result()
+ } else {
+ None
+ }
+ }
+}
+
+pub struct Calculator {
+ pub history: UnsafeHistory,
+}
+
+impl Calculator {
+ pub fn new() -> Self {
+ Self {
+ history: UnsafeHistory::new(),
+ }
+ }
+
+ pub fn evaluate(&mut self, expression: &str) -> Result<f64, String> {
+ if expression == "undo" {
+ return self
+ .history
+ .undo()
+ .ok_or_else(|| "Nothing to undo".to_string());
+ }
+
+ if expression == "redo" {
+ return self
+ .history
+ .redo()
+ .ok_or_else(|| "Nothing to redo".to_string());
+ }
+
+ let result = self.parse_and_evaluate(expression)?;
+
+ self.history.push(CalculatorState {
+ expression: expression.to_string(),
+ result,
+ });
+
+ Ok(result)
+ }
+
+ fn parse_and_evaluate(&self, expression: &str) -> Result<f64, String> {
+ todo!("Evaluation TBD")
+ }
+}
diff --git a/design_patterns_in_rust/bad_calculator_3/src/lib.rs b/design_patterns_in_rust/bad_calculator_3/src/lib.rs
@@ -0,0 +1,6 @@
+pub mod bad_example;
+pub mod bad_example_statics;
+pub mod bad_example_unsafe;
+pub mod better_example;
+pub mod better_example_no_statics;
+pub mod better_example_no_unsafe;
diff --git a/design_patterns_in_rust/bad_calculator_3/src/main.rs b/design_patterns_in_rust/bad_calculator_3/src/main.rs
@@ -0,0 +1,17 @@
+use bad_calculator_3::bad_example_unsafe::Calculator;
+
+fn main() -> Result<(), String> {
+ let mut calc = Calculator::new();
+
+ // Add calculations until we exceed capacity
+ for i in 0..15 {
+ calc.evaluate(&format!("{} + {}", i, i))?;
+ }
+
+ // Try to undo
+ println!("Current: {:?}", calc.history.current_result());
+ println!("Undo: {:?}", calc.evaluate("undo")?);
+ println!("Undo again: {:?}", calc.evaluate("undo")?);
+
+ Ok(())
+}
diff --git a/design_patterns_in_rust/good_calculator/Cargo.lock b/design_patterns_in_rust/good_calculator/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "good_calculator"
+version = "0.1.0"
diff --git a/design_patterns_in_rust/good_calculator/Cargo.toml b/design_patterns_in_rust/good_calculator/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "good_calculator"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
diff --git a/design_patterns_in_rust/good_calculator/src/builder.rs b/design_patterns_in_rust/good_calculator/src/builder.rs
@@ -0,0 +1,189 @@
+use crate::{TokenFactory, token::Token};
+
+/// This struct represents our final, immutable expression
+#[derive(Clone)]
+pub struct Expression<F: TokenFactory> {
+ tokens: Vec<Token<F::Number, F::Operator>>,
+ factory: F,
+}
+
+impl<F: TokenFactory> Expression<F> {
+ /// Defining builder on Expression rather than calling ExpressionBuilder::new
+ /// directly follows Rust convention: the type you're building provides the
+ /// entry point to its builder, this makes the API discoverable
+ pub fn builder(factory: F) -> ExpressionBuilder<F> {
+ ExpressionBuilder::new(factory)
+ }
+
+ pub fn evaluate(&self) -> Result<F::Number, String> {
+ todo!()
+ }
+
+ /// Prototype Pattern
+ pub fn quadratic_template(factory: F) -> Result<ExpressionBuilder<F>, String> {
+ Expression::builder(factory)
+ .number("1")?
+ .operator("*")?
+ .variable("x")
+ .operator("^")?
+ .number("2")?
+ .operator("+")?
+ .number("0")?
+ .operator("*")?
+ .variable("x")
+ .operator("+")?
+ .number("0")
+ }
+
+ /// Another Prototype Pattern
+ pub fn set_coefficient(&self, a: i64, b: f64) -> Result<(), String> {
+ todo!()
+ }
+}
+
+#[derive(Clone)]
+pub struct SubExpression<F: TokenFactory> {
+ pub tokens: Vec<Token<F::Number, F::Operator>>,
+ factory: F,
+}
+
+impl<F: TokenFactory> SubExpression<F> {
+ pub fn new(tokens: Vec<Token<F::Number, F::Operator>>, factory: F) -> Self {
+ Self { tokens, factory }
+ }
+
+ // Insert this subexpression into a larger expression
+ pub fn insert_into(self, builder: ExpressionBuilder<F>) -> ExpressionBuilder<F> {
+ builder.extend(self.tokens)
+ }
+}
+
+/// This struct manages the construction process
+pub struct ExpressionBuilder<F: TokenFactory> {
+ tokens: Vec<Token<F::Number, F::Operator>>,
+ factory: F,
+ paren_count: i32,
+}
+
+/// Implement the builder construction methods
+/// Each method takes ownership of self and then returns it after modification
+/// This enables method chaining while preventing accidental reuse of partially
+/// built expressions
+impl<F: TokenFactory> ExpressionBuilder<F> {
+ pub fn new(factory: F) -> Self {
+ Self {
+ tokens: Vec::new(),
+ factory,
+ paren_count: 0,
+ }
+ }
+
+ // This is to support inserting subexpression into a larger expression
+ pub fn extend(mut self, tokens: Vec<Token<F::Number, F::Operator>>) -> ExpressionBuilder<F> {
+ todo!()
+ }
+
+ // Add a number to the expression
+ pub fn number(mut self, value: &str) -> Result<Self, String> {
+ let num = self.factory.create_number(value)?;
+ self.tokens.push(Token::Number(num));
+ Ok(self)
+ }
+
+ // Add an operator
+ pub fn operator(mut self, op: &str) -> Result<Self, String> {
+ let op = self.factory.create_operator(op)?;
+ self.tokens.push(Token::Operator(op));
+ Ok(self)
+ }
+
+ // Add a variable
+ pub fn variable(mut self, name: &str) -> Self {
+ self.tokens.push(Token::Variable(name.to_string()));
+ self
+ }
+
+ // Open a parenthesis group
+ pub fn open_paren(mut self) -> Self {
+ self.tokens.push(Token::OpenParen);
+ self.paren_count += 1;
+ self
+ }
+
+ // Close a parenthesis group
+ // The guard error prevents invalid expression from being built
+ pub fn close_paren(mut self) -> Result<Self, String> {
+ if self.paren_count <= 0 {
+ return Err("Unmatched closing parenthesis".to_string());
+ }
+ self.tokens.push(Token::CloseParen);
+ self.paren_count -= 1;
+ Ok(self)
+ }
+
+ /// Specialized Builder Pattern
+ /// Function-application Pattern
+ pub fn function(mut self, func: &str, arg: &str) -> Result<Self, String> {
+ let func_op = self.factory.create_operator(func)?;
+ let arg_num = self.factory.create_number(arg)?;
+
+ self.tokens.push(Token::Operator(func_op));
+ self.tokens.push(Token::Number(arg_num));
+
+ Ok(self)
+ }
+
+ /// Specialized Builder Pattern
+ /// Constructs a complete binary expression in one call
+ pub fn binary_op(mut self, left: &str, op: &str, right: &str) -> Result<Self, String> {
+ let left_num = self.factory.create_number(left)?;
+ let op_token = self.factory.create_operator(op)?;
+ let right_num = self.factory.create_number(right)?;
+
+ self.tokens.extend([
+ Token::Number(left_num),
+ Token::Operator(op_token),
+ Token::Number(right_num),
+ ]);
+
+ Ok(self)
+ }
+
+ // Build the final expression
+ // This method consumes the build, by taking self by value, ensuring no
+ // further modifications can be made after building
+ pub fn build(self) -> Result<Expression<F>, String> {
+ if self.paren_count != 0 {
+ return Err("Unmatched parenthesis".to_string());
+ }
+
+ if self.tokens.is_empty() {
+ return Err("Empty expression".to_string());
+ }
+
+ // Validate expression structure
+ self.validate_expression()?;
+
+ Ok(Expression {
+ tokens: self.tokens,
+ factory: self.factory,
+ })
+ }
+
+ fn validate_expression(&self) -> Result<(), String> {
+ use Token::*;
+
+ // No consecutive operators
+ for window in self.tokens.windows(2) {
+ match (&window[0], &window[1]) {
+ (Operator(_), Operator(_)) => {
+ return Err("Consecutive operators".to_string());
+ }
+ _ => continue,
+ }
+ }
+
+ // More validation rules...
+ Ok(())
+ }
+}
diff --git a/design_patterns_in_rust/good_calculator/src/calculator.rs b/design_patterns_in_rust/good_calculator/src/calculator.rs
@@ -0,0 +1,138 @@
+use std::sync::Arc;
+
+use crate::{
+ TokenFactory,
+ factory::StandardFactory,
+ number::{AngleMode, NumberFormat},
+ token::Token,
+};
+
+// /// Singleton Pattern?
+// /// This is rarely the best solution in Rust
+// pub struct CalculatorSettings {
+// precision: u32,
+// angle_mode: AngleMode,
+// notation: NumberFormat,
+// }
+// impl CalculatorSettings {
+// pub fn instance() -> &'static mut Self {
+// static mut INSTANCE: Option<CalculatorSettings> = None;
+
+// // The traditional singleton pattern requires unsafe, because we're
+// // creating mutable static variable; it's also not thread-safe
+// unsafe {
+// INSTANCE.get_or_insert_with(|| Self {
+// precision: 10,
+// angle_mode: AngleMode::Radians,
+// notation: NumberFormat::Decimal,
+// })
+// }
+// }
+// }
+
+/// Instead of Singleton Pattern, we will build something more robust that
+/// actually does what we need it to do:
+/// - this struct replaces the mutable global state
+/// - the Default implementation provide sensible defaults
+/// - because this struct is Clone, each calculator can have its own copy,
+/// eliminating shared mutable state
+#[derive(Clone, Debug, Default)]
+pub struct CalculatorConfig {
+ precision: u32,
+ angle_mode: AngleMode,
+ notation: NumberFormat,
+}
+
+// Use Default trait instead
+// impl Default for CalculatorConfig {
+// fn default() -> Self {
+// Self {
+// precision: 10,
+// angle_mode: AngleMode::Radians,
+// notation: NumberFormat::Decimal,
+// }
+// }
+// }
+
+impl CalculatorConfig {
+ pub fn scientific() -> Self {
+ Self {
+ precision: 15,
+ angle_mode: AngleMode::Radians,
+ notation: NumberFormat::Scientific,
+ ..Default::default()
+ }
+ }
+
+ pub fn engineering() -> Self {
+ Self {
+ notation: NumberFormat::Engineering,
+ ..Default::default()
+ }
+ }
+}
+
+pub struct Calculator<F: TokenFactory> {
+ config: CalculatorConfig,
+ factory: F,
+ expression: Vec<Token<F::Number, F::Operator>>,
+}
+
+impl<F: TokenFactory> Calculator<F> {
+ pub fn new(factory: F) -> Self {
+ Self {
+ config: CalculatorConfig::default(),
+ factory,
+ expression: Vec::new(),
+ }
+ }
+
+ /// It takes in a configuration explicitly through its constructor
+ /// This dependency injection approach makes the calculator's requirement
+ /// visible in its API. It has no hidden global state that might change
+ /// unexpectedly
+ pub fn with_config(factory: F, config: CalculatorConfig) -> Self {
+ Self {
+ config,
+ factory,
+ expression: Vec::new(),
+ }
+ }
+
+ pub fn parse(&mut self, input: &str) -> Result<(), String> {
+ for token in input.split_whitespace() {
+ // Try operator first
+ if let Ok(op) = self.factory.create_operator(token) {
+ self.expression.push(Token::Operator(op));
+ continue;
+ }
+
+ // Must be a number then
+ let num = self.factory.create_number(token)?;
+ self.expression.push(Token::Number(num));
+ }
+
+ Ok(())
+ }
+}
+
+pub struct CalculatorPool {
+ shared_config: Arc<CalculatorConfig>,
+ calculators: Vec<Calculator<StandardFactory>>,
+}
+
+impl CalculatorPool {
+ pub fn new(config: CalculatorConfig) -> Self {
+ Self {
+ shared_config: Arc::new(config),
+ calculators: Vec::new(),
+ }
+ }
+
+ pub fn new_calculator(&mut self) -> Calculator<StandardFactory> {
+ // let calc = Calculator::with_config(StandardFactory, (*self.shared_config).clone());
+ // self.calculators.push(calc.clone());
+ // calc
+ todo!()
+ }
+}
diff --git a/design_patterns_in_rust/good_calculator/src/factory.rs b/design_patterns_in_rust/good_calculator/src/factory.rs
@@ -0,0 +1,63 @@
+use crate::{
+ TokenFactory,
+ number::{NumberFormat, ScientificNumber, StandardNumber},
+ operator::{Operator, ScientificOperator, StandardOperator},
+ token::Function,
+};
+
+#[derive(Clone)]
+pub struct StandardFactory;
+
+impl TokenFactory for StandardFactory {
+ type Number = StandardNumber;
+ type Operator = StandardOperator;
+
+ fn create_number(&self, s: &str) -> Result<Self::Number, String> {
+ s.parse::<f64>()
+ .map(StandardNumber)
+ .map_err(|_| format!("Invalid number: {}", s))
+ }
+
+ fn create_operator(&self, s: &str) -> Result<Self::Operator, String> {
+ match s {
+ "+" => Ok(StandardOperator(Operator::Add)),
+ "-" => Ok(StandardOperator(Operator::Subtract)),
+ "*" => Ok(StandardOperator(Operator::Multiply)),
+ "/" => Ok(StandardOperator(Operator::Divide)),
+ _ => Err(format!("Invalid operator: {}", s)),
+ }
+ }
+}
+
+pub struct ScientificFactory;
+
+impl TokenFactory for ScientificFactory {
+ type Number = ScientificNumber;
+ type Operator = ScientificOperator;
+
+ fn create_number(&self, s: &str) -> Result<Self::Number, String> {
+ // Handle both scientific and standard notation
+ if s.contains('e') || s.contains('E') {
+ s.parse::<f64>().map(|value| ScientificNumber {
+ value,
+ format: NumberFormat::Scientific,
+ })
+ } else {
+ s.parse::<f64>().map(|value| ScientificNumber {
+ value,
+ format: NumberFormat::Decimal,
+ })
+ }
+ .map_err(|_| format!("Invalid number: {}", s))
+ }
+
+ fn create_operator(&self, s: &str) -> Result<Self::Operator, String> {
+ // Scientific mode support more operators
+ match s {
+ "sin" => Ok(ScientificOperator::Function(Function::Sin)),
+ "cos" => Ok(ScientificOperator::Function(Function::Cos)),
+ // ...other scientifc operators
+ _ => Err(format!("Invalid operator: {}", s)),
+ }
+ }
+}
diff --git a/design_patterns_in_rust/good_calculator/src/lib.rs b/design_patterns_in_rust/good_calculator/src/lib.rs
@@ -0,0 +1,28 @@
+pub mod builder;
+pub mod calculator;
+pub mod factory;
+pub mod number;
+pub mod operator;
+pub mod token;
+
+/// This trait defines all number types across different calculator modes
+pub trait NumberToken {
+ fn value(&self) -> f64;
+ fn format(&self) -> String;
+}
+
+pub trait OperatorToken {
+ fn precedence(&self) -> u8;
+ fn evaluate(&self, args: &[f64]) -> Result<f64, String>;
+}
+
+/// TokenFactory trait that ties everything together. It ensures that tokens
+/// created by the factory are always compatible.
+pub trait TokenFactory {
+ /// The associated Number and Operator types are the key to type safety
+ type Number: NumberToken;
+ type Operator: OperatorToken;
+
+ fn create_number(&self, s: &str) -> Result<Self::Number, String>;
+ fn create_operator(&self, s: &str) -> Result<Self::Operator, String>;
+}
diff --git a/design_patterns_in_rust/good_calculator/src/main.rs b/design_patterns_in_rust/good_calculator/src/main.rs
@@ -0,0 +1,92 @@
+use good_calculator::{
+ builder::{Expression, SubExpression},
+ calculator::{Calculator, CalculatorConfig},
+ factory::{ScientificFactory, StandardFactory},
+ number::StandardNumber,
+ operator::{Operator, StandardOperator},
+ token::Token,
+};
+
+fn main() -> Result<(), String> {
+ // 1. Enum in Vec
+ //
+ // In Rust Vec has to be a single type, the Token enum conviniently allows
+ // this by providing a unified type that can hold any token variant
+ // let numbers = [
+ // Token::number(6.0),
+ // Token::operator(Operator::Add),
+ // Token::Number(3.0)
+ // ]
+
+ /// 2. Abstract Factory
+ ///
+ /// With the abstract factory pattern, we can create different kinds of
+ /// calculators just by specifying which factory we would like to use:
+ let standard_calc = Calculator::new(StandardFactory);
+ let scientific_calc = Calculator::new(ScientificFactory);
+
+ /// 3. Builder Pattern
+ ///
+ /// This is how the Builder Pattern and Abstract Factory Pattern compose:
+ /// the builder delegates token creation to whichever factory it was given
+ // With standard calculator factory
+ let expr = Expression::builder(StandardFactory)
+ .number("2")?
+ .operator("+")?
+ .open_paren()
+ .number("3")?
+ .operator("*")?
+ .number("4")?
+ .close_paren()?
+ .build();
+ // With scientific calculator factory
+ let expr = Expression::builder(ScientificFactory)
+ .number("1.23e-4")?
+ .operator("sin")?
+ .build()?;
+
+ /// 3. Specialized Builder Pattern
+ ///
+ /// This encapsulate common expression patterns, so that we can write our
+ /// expressions even more consicely
+ let expr = Expression::builder(StandardFactory)
+ .binary_op("2", "+", "3")?
+ .build()?;
+ let expr = Expression::builder(ScientificFactory)
+ .function("sin", "0.5")?
+ .build()?;
+
+ /// 4. Prototype Pattern
+ ///
+ /// The quadratic_template method creates a prototype with placeholder
+ /// coefficients. We can clone this template and modify the coefficients.
+ let expr = Expression::quadratic_template(StandardFactory)?.build()?;
+ let expr2 = expr.clone();
+ expr2.set_coefficient(2, -4.0)?;
+ /// The sub expression can be cloned and inserted into larger expressions
+ let squared = SubExpression::new(
+ vec![
+ Token::variable("x".to_string()),
+ Token::Operator(StandardOperator(Operator::Power)),
+ Token::Number(StandardNumber(2.0)),
+ ],
+ StandardFactory,
+ );
+ /// prototype patterns that use Clone trait naturally:
+ let expr1 = Expression::builder(StandardFactory)
+ .extend(squared.clone().tokens)
+ .operator("+")?
+ .number("1")?
+ .build()?;
+ let expr2 = Expression::builder(StandardFactory)
+ .number("2")?
+ .operator("*")?
+ .extend(squared.tokens)
+ .build()?;
+ /// use Default trait
+ let default_calc = Calculator::new(StandardFactory);
+ let scientific_calc =
+ Calculator::with_config(ScientificFactory, CalculatorConfig::scientific());
+
+ Ok(())
+}
diff --git a/design_patterns_in_rust/good_calculator/src/number.rs b/design_patterns_in_rust/good_calculator/src/number.rs
@@ -0,0 +1,77 @@
+use crate::NumberToken;
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct Number {
+ pub value: f64,
+ pub format: NumberFormat,
+}
+
+impl Number {
+ pub fn format(&self) -> String {
+ match self.format {
+ NumberFormat::Decimal => format!("{}", self.value),
+ NumberFormat::Scientific => format!("{:e}", self.value),
+ NumberFormat::Engineering => {
+ // Engineering notation adjusts exponent to be multiple of 3
+ let exp = self.value.abs().log10().floor();
+ let adj_exp = (exp - exp % 3.0).floor();
+ let coeff = self.value / 10_f64.powf(adj_exp);
+ format!("{}e{}", coeff, adj_exp)
+ }
+ }
+ }
+}
+
+#[derive(Debug, Default, Clone, PartialEq)]
+pub enum NumberFormat {
+ #[default]
+ Decimal,
+ Scientific,
+ Engineering,
+}
+
+#[derive(Debug, Clone, Default)]
+pub enum AngleMode {
+ Degrees,
+ #[default]
+ Radians,
+}
+
+#[derive(Clone)]
+pub struct StandardNumber(pub f64);
+
+impl NumberToken for StandardNumber {
+ fn value(&self) -> f64 {
+ self.0
+ }
+
+ fn format(&self) -> String {
+ format!("{}", self.0)
+ }
+}
+
+#[derive(Clone)]
+pub struct ScientificNumber {
+ pub value: f64,
+ pub format: NumberFormat,
+}
+
+/// Different factory families can have different internal representations
+impl NumberToken for ScientificNumber {
+ fn value(&self) -> f64 {
+ self.value
+ }
+
+ fn format(&self) -> String {
+ match self.format {
+ NumberFormat::Scientific => format!("{:e}", self.value),
+ NumberFormat::Engineering => {
+ let exp = self.value.abs().log10().floor();
+ let adj_exp = (exp - exp % 3.0).floor();
+ let coeff = self.value / 10_f64.powf(adj_exp);
+ format!("{}e{}", coeff, adj_exp)
+ }
+ _ => format!("{}", self.value),
+ }
+ }
+}
diff --git a/design_patterns_in_rust/good_calculator/src/operator.rs b/design_patterns_in_rust/good_calculator/src/operator.rs
@@ -0,0 +1,59 @@
+use crate::{OperatorToken, token::Function};
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum Operator {
+ Add,
+ Subtract,
+ Multiply,
+ Divide,
+ Power,
+ Root,
+ Factorial,
+}
+
+#[derive(Clone)]
+pub struct StandardOperator(pub Operator);
+
+impl OperatorToken for StandardOperator {
+ fn precedence(&self) -> u8 {
+ match self.0 {
+ Operator::Add | Operator::Subtract => 1,
+ Operator::Multiply | Operator::Divide => 2,
+ Operator::Power => 3,
+ Operator::Root | Operator::Factorial => 4,
+ }
+ }
+
+ fn evaluate(&self, args: &[f64]) -> Result<f64, String> {
+ match self.0 {
+ Operator::Add => Ok(args[0] + args[1]),
+ Operator::Subtract => Ok(args[0] - args[1]),
+ Operator::Multiply => Ok(args[0] * args[1]),
+ Operator::Divide => {
+ if args[1] == 0.0 {
+ Err("Division by zero".to_string())
+ } else {
+ Ok(args[0] / args[1])
+ }
+ }
+ // ... other operators
+ _ => Err("Operation not supported in standard mode".to_string()),
+ }
+ }
+}
+
+#[derive(Clone)]
+pub enum ScientificOperator {
+ Basic(Operator),
+ Function(Function),
+}
+
+impl OperatorToken for ScientificOperator {
+ fn precedence(&self) -> u8 {
+ todo!()
+ }
+
+ fn evaluate(&self, args: &[f64]) -> Result<f64, String> {
+ todo!()
+ }
+}
diff --git a/design_patterns_in_rust/good_calculator/src/token.rs b/design_patterns_in_rust/good_calculator/src/token.rs
@@ -0,0 +1,51 @@
+use std::borrow::Cow;
+
+/// Instead of using class hierarchy with factory methods for each type, in Rust
+/// we can take a more natural approach, using enums (which provide sum type
+/// functionality)
+///
+/// The Token enum is now generic over Number and Operator types to support
+/// different factory implementations.
+#[derive(Debug, Clone, PartialEq)]
+pub enum Token<N, O> {
+ Number(N),
+ Operator(O),
+ Function(Function),
+ Variable(String),
+ OpenParen,
+ CloseParen,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum Function {
+ Sqrt,
+ Sin,
+ Cos,
+ Tan,
+}
+
+/// Constructors for the different Token enum types
+impl<N, O> Token<N, O> {
+ pub fn function(func: Function) -> Self {
+ Self::Function(func)
+ }
+
+ pub fn variable(name: impl Into<String>) -> Self {
+ Self::Variable(name.into())
+ }
+}
+
+/// Using Cow for Strings and Slices - Prototype Pattern
+pub struct VariableToken<'a> {
+ name: Cow<'a, str>,
+ value: f64,
+}
+
+impl<'a> VariableToken<'a> {
+ pub fn new(name: impl Into<Cow<'a, str>>, value: f64) -> Self {
+ Self {
+ name: name.into(),
+ value,
+ }
+ }
+}
diff --git a/design_patterns_in_rust/good_calculator_2/Cargo.lock b/design_patterns_in_rust/good_calculator_2/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "good_calculator"
+version = "0.1.0"
diff --git a/design_patterns_in_rust/good_calculator_2/Cargo.toml b/design_patterns_in_rust/good_calculator_2/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "good_calculator"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
diff --git a/design_patterns_in_rust/good_calculator_2/src/adapters.rs b/design_patterns_in_rust/good_calculator_2/src/adapters.rs
@@ -0,0 +1,114 @@
+//! Adapter Pattern
+
+use std::{collections::HashMap, f64::consts::PI};
+
+use crate::{number::AngleMode, token::VariableToken, utilities::Expression};
+
+pub trait ScientificOperations {
+ fn sin(&self, angle: f64) -> f64;
+ fn cos(&self, angle: f64) -> f64;
+ fn tan(&self, angle: f64) -> f64;
+ fn log(&self, value: f64, base: f64) -> Result<f64, String>;
+}
+
+pub struct StandardScientificOperations {
+ pub angle_mode: AngleMode,
+}
+
+impl ScientificOperations for StandardScientificOperations {
+ fn sin(&self, angle: f64) -> f64 {
+ match self.angle_mode {
+ AngleMode::Radians => angle.sin(),
+ AngleMode::Degrees => (angle * PI / 180.0).sin(),
+ }
+ }
+
+ fn cos(&self, angle: f64) -> f64 {
+ match self.angle_mode {
+ AngleMode::Radians => angle.cos(),
+ AngleMode::Degrees => (angle * PI / 180.0).cos(),
+ }
+ }
+
+ fn tan(&self, angle: f64) -> f64 {
+ todo!()
+ }
+
+ fn log(&self, value: f64, base: f64) -> Result<f64, String> {
+ if value <= 0.0 {
+ return Err("Cannot take logarithm of non-positive number".to_string());
+ }
+ if base <= 0.0 || base == 1.0 {
+ return Err("Invalid logarithm base".to_string());
+ }
+ Ok(value.ln() / base.ln())
+ }
+}
+
+pub struct ExternalLibraryAdapter {
+ angle_mode: AngleMode,
+}
+
+impl ExternalLibraryAdapter {
+ pub fn new(angle_mode: AngleMode) -> Self {
+ Self { angle_mode }
+ }
+
+ fn convert_angle(&self, angle: f64) -> f64 {
+ match self.angle_mode {
+ AngleMode::Radians => angle,
+ AngleMode::Degrees => angle * PI / 180.0,
+ }
+ }
+}
+
+impl ScientificOperations for ExternalLibraryAdapter {
+ fn sin(&self, angle: f64) -> f64 {
+ let converted = self.convert_angle(angle);
+ converted.sin()
+ }
+
+ fn cos(&self, angle: f64) -> f64 {
+ todo!()
+ }
+
+ fn tan(&self, angle: f64) -> f64 {
+ todo!()
+ }
+
+ fn log(&self, value: f64, base: f64) -> Result<f64, String> {
+ todo!()
+ }
+}
+
+/// This struct uses a closure (Box<dyn Fn(f64) -> f64) to capture a specific
+/// scientific operation.
+pub struct ScientificFunctionExpression {
+ operation: Box<dyn Fn(f64) -> f64>,
+ arg_expression: Box<dyn Expression>,
+ description: String,
+}
+
+impl Expression for ScientificFunctionExpression {
+ fn evaluate(&self, variables: &HashMap<String, f64>) -> Result<f64, String> {
+ let arg_value = self.arg_expression.evaluate(variables)?;
+ Ok((self.operation)(arg_value))
+ }
+
+ fn to_string(&self) -> String {
+ format!("{}({})", self.description, self.arg_expression.to_string())
+ }
+}
+
+/// The move keyword transfers ownership of the `sci_ops` trait object into
+/// the closure.
+impl ScientificFunctionExpression {
+ pub fn new_sin(sci_ops: Box<dyn ScientificOperations>, arg: Box<dyn Expression>) -> Self {
+ let operation = Box::new(move |angle: f64| sci_ops.sin(angle));
+ Self {
+ operation,
+ arg_expression: arg,
+ description: "sin".to_string(),
+ }
+ }
+}
diff --git a/design_patterns_in_rust/good_calculator_2/src/bridge_pattern.rs b/design_patterns_in_rust/good_calculator_2/src/bridge_pattern.rs
@@ -0,0 +1,88 @@
+//! The Bridge Pattern:
+//! The abstraction defines what the display can do. The implementation defines
+//! how formatting works.
+
+use crate::Expression;
+
+/// The Display trait speaks in calculator domain concepts: results, errors,
+/// expressions.
+pub trait Display {
+ fn show_result(&self, result: f64);
+ fn show_error(&self, error: &str);
+ fn show_expression(&self, expression: &dyn Expression);
+}
+
+/// The DisplayImplementation trait spesaks in formatting primitives: text
+/// and formatted values.
+pub trait DisplayImplementation {
+ fn display_text(&self, text: &str);
+ fn display_formatted(&self, value: f64, format: &str);
+}
+
+/// The Bridge connects them through composition:
+pub struct CalculatorDisplay {
+ implementation: Box<dyn DisplayImplementation>,
+}
+
+impl Display for CalculatorDisplay {
+ fn show_result(&self, result: f64) {
+ self.implementation
+ .display_formatted(result, "Result: {:.10g}");
+ }
+
+ fn show_error(&self, error: &str) {
+ self.implementation
+ .display_text(&format!("Error: {}", error));
+ }
+
+ fn show_expression(&self, expression: &dyn Expression) {
+ self.implementation
+ .display_text(&format!("Expression: {}", expression.to_string()));
+ }
+}
+
+/// Concrete output mechanisms:
+///
+/// The console implementation writes plain text to standard output:
+pub struct ConsoleDisplay;
+
+impl DisplayImplementation for ConsoleDisplay {
+ fn display_text(&self, text: &str) {
+ println!("{}", text);
+ }
+
+ fn display_formatted(&self, value: f64, format: &str) {
+ println!("{}", format.replace("{:.10g}", &format!("{:.10}", value)))
+ }
+}
+
+/// The HTML implementation wraps output in HTML elements:
+pub struct HtmlDisplay;
+
+impl DisplayImplementation for HtmlDisplay {
+ fn display_text(&self, text: &str) {
+ println!(
+ "<div>{}</div>",
+ text.replace("<", "<").replace(">", ">")
+ );
+ }
+
+ fn display_formatted(&self, value: f64, format: &str) {
+ let formatted = format.replace("{:.10g}", &format!("{:.10}", value));
+ println!("<div class=\"result\">{}</div>", formatted);
+ }
+}
+
+/// The JSON implementation produces machine-readable output, making it easy
+/// to use the output in other programs
+pub struct JsonDisplay;
+
+impl DisplayImplementation for JsonDisplay {
+ fn display_text(&self, text: &str) {
+ println!("{{\"text\": \"{}\"}}", text.replace("\"", "\\\""));
+ }
+
+ fn display_formatted(&self, value: f64, format: &str) {
+ println!("{{\"result\": {:.10}}}", value);
+ }
+}
diff --git a/design_patterns_in_rust/good_calculator_2/src/builder.rs b/design_patterns_in_rust/good_calculator_2/src/builder.rs
@@ -0,0 +1,191 @@
+use crate::{TokenFactory, token::Token};
+
+/// This struct represents our final, immutable expression
+#[derive(Clone)]
+pub struct Expression<F: TokenFactory> {
+ tokens: Vec<Token<F::Number, F::Operator>>,
+ factory: F,
+}
+
+impl<F: TokenFactory> Expression<F> {
+ /// Defining builder on Expression rather than calling ExpressionBuilder::new
+ /// directly follows Rust convention: the type you're building provides the
+ /// entry point to its builder, this makes the API discoverable
+ pub fn builder(factory: F) -> ExpressionBuilder<F> {
+ ExpressionBuilder::new(factory)
+ }
+
+ pub fn evaluate(&self) -> Result<F::Number, String> {
+ todo!()
+ }
+
+ /// Prototype Pattern
+ pub fn quadratic_template(factory: F) -> Result<ExpressionBuilder<F>, String> {
+ Expression::builder(factory)
+ .number("1")?
+ .operator("*")?
+ .variable("x")
+ .operator("^")?
+ .number("2")?
+ .operator("+")?
+ .number("0")?
+ .operator("*")?
+ .variable("x")
+ .operator("+")?
+ .number("0")
+ }
+
+ /// Another Prototype Pattern
+ pub fn set_coefficient(&self, a: i64, b: f64) -> Result<(), String> {
+ todo!()
+ }
+}
+
+#[derive(Clone)]
+pub struct SubExpression<F: TokenFactory> {
+ pub tokens: Vec<Token<F::Number, F::Operator>>,
+ factory: F,
+}
+
+impl<F: TokenFactory> SubExpression<F> {
+ pub fn new(tokens: Vec<Token<F::Number, F::Operator>>, factory: F) -> Self {
+ Self { tokens, factory }
+ }
+
+ // Insert this subexpression into a larger expression
+ pub fn insert_into(self, builder: ExpressionBuilder<F>) -> ExpressionBuilder<F> {
+ builder.extend(self.tokens)
+ }
+}
+
+/// This struct manages the construction process
+pub struct ExpressionBuilder<F: TokenFactory> {
+ tokens: Vec<Token<F::Number, F::Operator>>,
+ factory: F,
+ paren_count: i32,
+}
+
+/// Implement the builder construction methods
+/// Each method takes ownership of self and then returns it after modification
+/// This enables method chaining while preventing accidental reuse of partially
+/// built expressions
+impl<F: TokenFactory> ExpressionBuilder<F> {
+ pub fn new(factory: F) -> Self {
+ Self {
+ tokens: Vec::new(),
+ factory,
+ paren_count: 0,
+ }
+ }
+
+ // This is to support inserting subexpression into a larger expression
+ pub fn extend(mut self, tokens: Vec<Token<F::Number, F::Operator>>) -> ExpressionBuilder<F> {
+ todo!()
+ }
+
+ // Add a number to the expression
+ pub fn number(mut self, value: &str) -> Result<Self, String> {
+ let num = self.factory.create_number(value)?;
+ self.tokens.push(Token::Number(num));
+ Ok(self)
+ }
+
+ // Add an operator
+ pub fn operator(mut self, op: &str) -> Result<Self, String> {
+ let op = self.factory.create_operator(op)?;
+ self.tokens.push(Token::Operator(op));
+ Ok(self)
+ }
+
+ // Add a variable
+ pub fn variable(mut self, name: &str) -> Self {
+ self.tokens.push(Token::Variable(name.to_string()));
+ self
+ }
+
+ // Open a parenthesis group
+ pub fn open_paren(mut self) -> Self {
+ self.tokens.push(Token::OpenParen);
+ self.paren_count += 1;
+ self
+ }
+
+ // Close a parenthesis group
+ // The guard error prevents invalid expression from being built
+ pub fn close_paren(mut self) -> Result<Self, String> {
+ if self.paren_count <= 0 {
+ return Err("Unmatched closing parenthesis".to_string());
+ }
+ self.tokens.push(Token::CloseParen);
+ self.paren_count -= 1;
+ Ok(self)
+ }
+
+ /// Specialized Builder Pattern
+ /// Function-application Pattern
+ pub fn function(mut self, func: &str, arg: &str) -> Result<Self, String> {
+ let func_op = self.factory.create_operator(func)?;
+ let arg_num = self.factory.create_number(arg)?;
+
+ self.tokens.push(Token::Operator(func_op));
+ self.tokens.push(Token::Number(arg_num));
+
+ Ok(self)
+ }
+
+ /// Specialized Builder Pattern
+ /// Constructs a complete binary expression in one call
+ pub fn binary_op(mut self, left: &str, op: &str, right: &str) -> Result<Self, String> {
+ let left_num = self.factory.create_number(left)?;
+ let op_token = self.factory.create_operator(op)?;
+ let right_num = self.factory.create_number(right)?;
+
+ self.tokens.extend([
+ Token::Number(left_num),
+ Token::Operator(op_token),
+ Token::Number(right_num),
+ ]);
+
+ Ok(self)
+ }
+
+ // Build the final expression
+ // This method consumes the build, by taking self by value, ensuring no
+ // further modifications can be made after building
+ pub fn build(self) -> Result<Expression<F>, String> {
+ if self.paren_count != 0 {
+ return Err("Unmatched parenthesis".to_string());
+ }
+
+ if self.tokens.is_empty() {
+ return Err("Empty expression".to_string());
+ }
+
+ // Validate expression structure
+ self.validate_expression()?;
+
+ Ok(Expression {
+ tokens: self.tokens,
+ factory: self.factory,
+ })
+ }
+
+ fn validate_expression(&self) -> Result<(), String> {
+ use Token::*;
+
+ // No consecutive operators
+ for window in self.tokens.windows(2) {
+ match (&window[0], &window[1]) {
+ (Operator(_), Operator(_)) => {
+ return Err("Consecutive operators".to_string());
+ }
+ _ => continue,
+ }
+ }
+
+ // More validation rules...
+ Ok(())
+ }
+}
+
+pub struct ExpressionParser;
diff --git a/design_patterns_in_rust/good_calculator_2/src/calculator.rs b/design_patterns_in_rust/good_calculator_2/src/calculator.rs
@@ -0,0 +1,122 @@
+use std::sync::Arc;
+
+use crate::{
+ TokenFactory,
+ factory::StandardFactory,
+ number::{AngleMode, NumberFormat},
+ token::Token,
+};
+
+/// Instead of Singleton Pattern, we will build something more robust that
+/// actually does what we need it to do:
+/// - this struct replaces the mutable global state
+/// - the Default implementation provide sensible defaults
+/// - because this struct is Clone, each calculator can have its own copy,
+/// eliminating shared mutable state
+#[derive(Clone, Debug, Default)]
+pub struct CalculatorConfig {
+ precision: u32,
+ angle_mode: AngleMode,
+ notation: NumberFormat,
+}
+
+// Use Default trait instead
+// impl Default for CalculatorConfig {
+// fn default() -> Self {
+// Self {
+// precision: 10,
+// angle_mode: AngleMode::Radians,
+// notation: NumberFormat::Decimal,
+// }
+// }
+// }
+
+impl CalculatorConfig {
+ pub fn scientific() -> Self {
+ Self {
+ precision: 15,
+ angle_mode: AngleMode::Radians,
+ notation: NumberFormat::Scientific,
+ ..Default::default()
+ }
+ }
+
+ pub fn engineering() -> Self {
+ Self {
+ notation: NumberFormat::Engineering,
+ ..Default::default()
+ }
+ }
+}
+
+pub struct Calculator<F: TokenFactory> {
+ config: CalculatorConfig,
+ factory: F,
+ expression: Vec<Token<F::Number, F::Operator>>,
+}
+
+impl<F: TokenFactory> Calculator<F> {
+ pub fn new(factory: F) -> Self {
+ Self {
+ config: CalculatorConfig::default(),
+ factory,
+ expression: Vec::new(),
+ }
+ }
+
+ /// It takes in a configuration explicitly through its constructor
+ /// This dependency injection approach makes the calculator's requirement
+ /// visible in its API. It has no hidden global state that might change
+ /// unexpectedly
+ pub fn with_config(factory: F, config: CalculatorConfig) -> Self {
+ Self {
+ config,
+ factory,
+ expression: Vec::new(),
+ }
+ }
+
+ pub fn parse(&mut self, input: &str) -> Result<(), String> {
+ for token in input.split_whitespace() {
+ // Try operator first
+ if let Ok(op) = self.factory.create_operator(token) {
+ self.expression.push(Token::Operator(op));
+ continue;
+ }
+
+ // Must be a number then
+ let num = self.factory.create_number(token)?;
+ self.expression.push(Token::Number(num));
+ }
+
+ Ok(())
+ }
+}
+
+pub struct CalculatorPool {
+ shared_config: Arc<CalculatorConfig>,
+ calculators: Vec<Calculator<StandardFactory>>,
+}
+
+impl CalculatorPool {
+ pub fn new(config: CalculatorConfig) -> Self {
+ Self {
+ shared_config: Arc::new(config),
+ calculators: Vec::new(),
+ }
+ }
+
+ pub fn new_calculator(&mut self) -> Calculator<StandardFactory> {
+ // let calc = Calculator::with_config(StandardFactory, (*self.shared_config).clone());
+ // self.calculators.push(calc.clone());
+ // calc
+ todo!()
+ }
+
+ /// Flyweight Pattern:
+ /// For shared ownerhips of data that needs to live as long as any of its
+ /// users, Arc (atomic reference counting) provides thread-safe sharing:
+ pub fn get_config(&self) -> Arc<CalculatorConfig> {
+ Arc::clone(&self.shared_config)
+ }
+}
diff --git a/design_patterns_in_rust/good_calculator_2/src/facade.rs b/design_patterns_in_rust/good_calculator_2/src/facade.rs
@@ -0,0 +1,55 @@
+//! The Facade pattern provides a simplified interface to a complex subsystem,
+//! hiding the details of multiple interacting components behind a single,
+//! easy-to-use API.
+
+use std::collections::HashMap;
+
+use crate::{
+ adapters::ScientificOperations, builder::ExpressionParser, calculator::CalculatorConfig,
+};
+
+pub struct CalculatorFacade {
+ parser: ExpressionParser,
+ variables: HashMap<String, f64>,
+ scientific_ops: Box<dyn ScientificOperations>,
+ history: Vec<String>,
+ config: CalculatorConfig,
+}
+
+impl CalculatorFacade {
+ pub fn new(scientific_ops: Box<dyn ScientificOperations>, config: CalculatorConfig) -> Self {
+ Self {
+ parser: ExpressionParser,
+ variables: HashMap::new(),
+ scientific_ops,
+ history: Vec::new(),
+ config,
+ }
+ }
+
+ pub fn evaluate(&mut self, expression: &str) -> Result<f64, String> {
+ self.history.push(expression.to_string());
+
+ todo!()
+ }
+
+ pub fn get_variable(&mut self, name: &str, value: f64) {
+ self.variables.insert(name.to_string(), value);
+ }
+
+ pub fn calculate_quadric(&self, a: f64, b: f64, c: f64) -> Result<(f64, f64), String> {
+ let discriminant = b * b - 4.0 * a * c;
+ if discriminant < 0.0 {
+ return Err("No real solutions".to_string());
+ }
+ let sqrt_d = discriminant.sqrt();
+ let x1 = (-b + sqrt_d) / (2.0 * a);
+ let x2 = (-b - sqrt_d) / (2.0 * a);
+
+ Ok((x1, x2))
+ }
+
+ pub fn calculate_pythagorean(&self, a: f64, b: f64) -> f64 {
+ (a * a + b * b).sqrt()
+ }
+}
diff --git a/design_patterns_in_rust/good_calculator_2/src/factory.rs b/design_patterns_in_rust/good_calculator_2/src/factory.rs
@@ -0,0 +1,63 @@
+use crate::{
+ TokenFactory,
+ number::{NumberFormat, ScientificNumber, StandardNumber},
+ operator::{Operator, ScientificOperator, StandardOperator},
+ token::Function,
+};
+
+#[derive(Clone)]
+pub struct StandardFactory;
+
+impl TokenFactory for StandardFactory {
+ type Number = StandardNumber;
+ type Operator = StandardOperator;
+
+ fn create_number(&self, s: &str) -> Result<Self::Number, String> {
+ s.parse::<f64>()
+ .map(StandardNumber)
+ .map_err(|_| format!("Invalid number: {}", s))
+ }
+
+ fn create_operator(&self, s: &str) -> Result<Self::Operator, String> {
+ match s {
+ "+" => Ok(StandardOperator(Operator::Add)),
+ "-" => Ok(StandardOperator(Operator::Subtract)),
+ "*" => Ok(StandardOperator(Operator::Multiply)),
+ "/" => Ok(StandardOperator(Operator::Divide)),
+ _ => Err(format!("Invalid operator: {}", s)),
+ }
+ }
+}
+
+pub struct ScientificFactory;
+
+impl TokenFactory for ScientificFactory {
+ type Number = ScientificNumber;
+ type Operator = ScientificOperator;
+
+ fn create_number(&self, s: &str) -> Result<Self::Number, String> {
+ // Handle both scientific and standard notation
+ if s.contains('e') || s.contains('E') {
+ s.parse::<f64>().map(|value| ScientificNumber {
+ value,
+ format: NumberFormat::Scientific,
+ })
+ } else {
+ s.parse::<f64>().map(|value| ScientificNumber {
+ value,
+ format: NumberFormat::Decimal,
+ })
+ }
+ .map_err(|_| format!("Invalid number: {}", s))
+ }
+
+ fn create_operator(&self, s: &str) -> Result<Self::Operator, String> {
+ // Scientific mode support more operators
+ match s {
+ "sin" => Ok(ScientificOperator::Function(Function::Sin)),
+ "cos" => Ok(ScientificOperator::Function(Function::Cos)),
+ // ...other scientifc operators
+ _ => Err(format!("Invalid operator: {}", s)),
+ }
+ }
+}
diff --git a/design_patterns_in_rust/good_calculator_2/src/lib.rs b/design_patterns_in_rust/good_calculator_2/src/lib.rs
@@ -0,0 +1,44 @@
+mod adapters;
+mod bridge_pattern;
+mod builder;
+mod calculator;
+mod facade;
+mod factory;
+mod number;
+mod operator;
+mod token;
+mod utilities;
+
+pub use operator::{BinaryOperation, Operator};
+/// Modules and Crates as Facade Pattern
+///
+/// By controlling what a module or crate exports through `pub use` re-exports,
+/// you create a curated public API that hides internal complexity.
+///
+/// This lib.rs file acts as a facade at the crate level.
+pub use utilities::{
+ CachingExpression, ConsoleLogger, Expression, LoggingExpression, NumberExpression,
+ TimingExpression,
+};
+
+/// This trait defines all number types across different calculator modes
+pub trait NumberToken {
+ fn value(&self) -> f64;
+ fn format(&self) -> String;
+}
+
+pub trait OperatorToken {
+ fn precedence(&self) -> u8;
+ fn evaluate(&self, args: &[f64]) -> Result<f64, String>;
+}
+
+/// TokenFactory trait that ties everything together. It ensures that tokens
+/// created by the factory are always compatible.
+pub trait TokenFactory {
+ /// The associated Number and Operator types are the key to type safety
+ type Number: NumberToken;
+ type Operator: OperatorToken;
+
+ fn create_number(&self, s: &str) -> Result<Self::Number, String>;
+ fn create_operator(&self, s: &str) -> Result<Self::Operator, String>;
+}
diff --git a/design_patterns_in_rust/good_calculator_2/src/main.rs b/design_patterns_in_rust/good_calculator_2/src/main.rs
@@ -0,0 +1,43 @@
+use std::collections::HashMap;
+
+use good_calculator::{
+ BinaryOperation, CachingExpression, ConsoleLogger, Expression, LoggingExpression,
+ NumberExpression, Operator, TimingExpression,
+};
+
+fn main() -> Result<(), String> {
+ /// Structural Patterns
+ ///
+ /// 1. Decorator Pattern
+ let expr = Box::new(NumberExpression::new(42.0));
+ let cached = Box::new(CachingExpression::new(expr));
+ let timed = Box::new(TimingExpression::new(cached));
+ let logged = LoggingExpression::new(timed, Box::new(ConsoleLogger));
+ // When evaluate is called on the outermost decorator, the call flows through
+ // each layer. The order of wrapping matters.
+ let result = logged.evaluate(&HashMap::new());
+
+ /// 2. Composite Pattern
+ /// Because all nodes implement Expression, Decorator Pattern work seamlessly
+ /// with Composite Pattern trees.
+ /// The tree structure encodes operator precedence directly:
+ let multiply = Box::new(BinaryOperation::new(
+ Box::new(NumberExpression::new(3.0)),
+ Box::new(NumberExpression::new(4.0)),
+ Operator::Multiply,
+ ));
+ let add = Box::new(BinaryOperation::new(
+ Box::new(NumberExpression::new(2.0)),
+ multiply,
+ Operator::Add,
+ ));
+ /// Evaluating the tree is a single method call:
+ let variables = HashMap::new();
+ println!("Expression: {}", add.to_string());
+ match add.evaluate(&variables) {
+ Ok(result) => println!("Result: {}", result),
+ Err(error) => eprintln!("Error: {}", error),
+ }
+
+ Ok(())
+}
diff --git a/design_patterns_in_rust/good_calculator_2/src/number.rs b/design_patterns_in_rust/good_calculator_2/src/number.rs
@@ -0,0 +1,77 @@
+use crate::NumberToken;
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct Number {
+ pub value: f64,
+ pub format: NumberFormat,
+}
+
+impl Number {
+ pub fn format(&self) -> String {
+ match self.format {
+ NumberFormat::Decimal => format!("{}", self.value),
+ NumberFormat::Scientific => format!("{:e}", self.value),
+ NumberFormat::Engineering => {
+ // Engineering notation adjusts exponent to be multiple of 3
+ let exp = self.value.abs().log10().floor();
+ let adj_exp = (exp - exp % 3.0).floor();
+ let coeff = self.value / 10_f64.powf(adj_exp);
+ format!("{}e{}", coeff, adj_exp)
+ }
+ }
+ }
+}
+
+#[derive(Debug, Default, Clone, PartialEq)]
+pub enum NumberFormat {
+ #[default]
+ Decimal,
+ Scientific,
+ Engineering,
+}
+
+#[derive(Debug, Clone, Default)]
+pub enum AngleMode {
+ Degrees,
+ #[default]
+ Radians,
+}
+
+#[derive(Clone)]
+pub struct StandardNumber(pub f64);
+
+impl NumberToken for StandardNumber {
+ fn value(&self) -> f64 {
+ self.0
+ }
+
+ fn format(&self) -> String {
+ format!("{}", self.0)
+ }
+}
+
+#[derive(Clone)]
+pub struct ScientificNumber {
+ pub value: f64,
+ pub format: NumberFormat,
+}
+
+/// Different factory families can have different internal representations
+impl NumberToken for ScientificNumber {
+ fn value(&self) -> f64 {
+ self.value
+ }
+
+ fn format(&self) -> String {
+ match self.format {
+ NumberFormat::Scientific => format!("{:e}", self.value),
+ NumberFormat::Engineering => {
+ let exp = self.value.abs().log10().floor();
+ let adj_exp = (exp - exp % 3.0).floor();
+ let coeff = self.value / 10_f64.powf(adj_exp);
+ format!("{}e{}", coeff, adj_exp)
+ }
+ _ => format!("{}", self.value),
+ }
+ }
+}
diff --git a/design_patterns_in_rust/good_calculator_2/src/operator.rs b/design_patterns_in_rust/good_calculator_2/src/operator.rs
@@ -0,0 +1,186 @@
+use std::{collections::HashMap, f32::consts::FRAC_PI_2};
+
+use crate::{Expression, OperatorToken, token::Function};
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum Operator {
+ Add,
+ Subtract,
+ Multiply,
+ Divide,
+ Power,
+ Root,
+ Factorial,
+}
+
+#[derive(Clone)]
+pub struct StandardOperator(pub Operator);
+
+impl OperatorToken for StandardOperator {
+ fn precedence(&self) -> u8 {
+ match self.0 {
+ Operator::Add | Operator::Subtract => 1,
+ Operator::Multiply | Operator::Divide => 2,
+ Operator::Power => 3,
+ Operator::Root | Operator::Factorial => 4,
+ }
+ }
+
+ fn evaluate(&self, args: &[f64]) -> Result<f64, String> {
+ match self.0 {
+ Operator::Add => Ok(args[0] + args[1]),
+ Operator::Subtract => Ok(args[0] - args[1]),
+ Operator::Multiply => Ok(args[0] * args[1]),
+ Operator::Divide => {
+ if args[1] == 0.0 {
+ Err("Division by zero".to_string())
+ } else {
+ Ok(args[0] / args[1])
+ }
+ }
+ // ... other operators
+ _ => Err("Operation not supported in standard mode".to_string()),
+ }
+ }
+}
+
+#[derive(Clone)]
+pub enum ScientificOperator {
+ Basic(Operator),
+ Function(Function),
+}
+
+impl OperatorToken for ScientificOperator {
+ fn precedence(&self) -> u8 {
+ todo!()
+ }
+
+ fn evaluate(&self, args: &[f64]) -> Result<f64, String> {
+ todo!()
+ }
+}
+
+/// Composite Pattern: this is a composite node, it holds two child expressions
+/// and an operator. The children are `Box<dyn Expression>` trait objects, which
+/// means each child can be any expression type: a number, a variable, another
+/// binary operation, or even a decorated expression.
+///
+/// We use `Box<dyn Expression>` rather than generic type parameters because
+/// generic would make each `BinaryOperation` monomorphic (specialized to one
+/// concrete type) over its children's types.
+pub struct BinaryOperation {
+ pub left: Box<dyn Expression>,
+ pub right: Box<dyn Expression>,
+ pub operator: Operator,
+}
+
+impl BinaryOperation {
+ pub fn new(left: Box<dyn Expression>, right: Box<dyn Expression>, operator: Operator) -> Self {
+ Self {
+ left,
+ right,
+ operator,
+ }
+ }
+
+ fn operator_symbol(&self) -> &'static str {
+ match self.operator {
+ Operator::Add => "+",
+ Operator::Subtract => "-",
+ Operator::Multiply => "*",
+ Operator::Divide => "/",
+ Operator::Power => "^",
+ Operator::Root => todo!(),
+ Operator::Factorial => todo!(),
+ }
+ }
+}
+
+impl Expression for BinaryOperation {
+ fn evaluate(&self, variables: &std::collections::HashMap<String, f64>) -> Result<f64, String> {
+ // Evaluates both children recursively
+ let l = self.left.evaluate(variables)?;
+ let r = self.right.evaluate(variables)?;
+
+ match self.operator {
+ Operator::Add => Ok(l + r),
+ Operator::Subtract => Ok(l - r),
+ Operator::Multiply => Ok(l * r),
+ Operator::Divide if r == 0.0 => Err("Division by zero".to_string()),
+ Operator::Divide => Ok(l / r),
+ Operator::Power => Ok(l.powf(r)),
+ _ => Ok(l + r),
+ }
+ }
+
+ fn to_string(&self) -> String {
+ let left_str = if self.left.precedence() < self.precedence() {
+ format!("({})", self.left.to_string())
+ } else {
+ self.left.to_string()
+ };
+
+ let right_str = if self.right.precedence() < self.precedence() {
+ format!("({})", self.right.to_string())
+ } else {
+ self.right.to_string()
+ };
+
+ format!("{} {} {}", left_str, self.operator_symbol(), right_str)
+ }
+
+ fn precedence(&self) -> u8 {
+ match self.operator {
+ Operator::Add | Operator::Subtract => 1,
+ Operator::Multiply | Operator::Divide => 2,
+ Operator::Power => 3,
+ Operator::Root => todo!(),
+ Operator::Factorial => todo!(),
+ }
+ }
+}
+
+/// Like BinaryOperation, FunctionCall demonstrates the recursive nature of
+/// the Composite pattern: the argument can itself be an arbitrarily complex
+/// expression tree, and the uniform Expression interface handles any depth
+/// of nesting.
+pub struct FunctionCall {
+ pub function: Function,
+ pub argument: Box<dyn Expression>,
+}
+
+impl Expression for FunctionCall {
+ fn evaluate(&self, variables: &HashMap<String, f64>) -> Result<f64, String> {
+ let val = self.argument.evaluate(variables)?;
+
+ match self.function {
+ Function::Sin => Ok(val.sin()),
+ Function::Cos => Ok(val.cos()),
+ Function::Tan => {
+ let hp = std::f64::consts::FRAC_PI_2;
+ if (val - hp).abs() % std::f64::consts::PI < 1e-10 {
+ Err("Targent undefined at this value".into())
+ } else {
+ Ok(val.tan())
+ }
+ }
+ Function::Sqrt if val < 0.0 => Err("Cannot take square root of negative number".into()),
+ Function::Sqrt => Ok(val.sqrt()),
+ }
+ }
+
+ /// This method reconstructs the function call syntax for display purpose
+ fn to_string(&self) -> String {
+ let func_name = match self.function {
+ Function::Sqrt => "sqrt",
+ Function::Sin => "sin",
+ Function::Cos => "cos",
+ Function::Tan => "tan",
+ };
+ format!("{}({})", func_name, self.argument.to_string())
+ }
+
+ fn precedence(&self) -> u8 {
+ 4
+ }
+}
diff --git a/design_patterns_in_rust/good_calculator_2/src/token.rs b/design_patterns_in_rust/good_calculator_2/src/token.rs
@@ -0,0 +1,51 @@
+use std::borrow::Cow;
+
+/// Instead of using class hierarchy with factory methods for each type, in Rust
+/// we can take a more natural approach, using enums (which provide sum type
+/// functionality)
+///
+/// The Token enum is now generic over Number and Operator types to support
+/// different factory implementations.
+#[derive(Debug, Clone, PartialEq)]
+pub enum Token<N, O> {
+ Number(N),
+ Operator(O),
+ Function(Function),
+ Variable(String),
+ OpenParen,
+ CloseParen,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum Function {
+ Sqrt,
+ Sin,
+ Cos,
+ Tan,
+}
+
+/// Constructors for the different Token enum types
+impl<N, O> Token<N, O> {
+ pub fn function(func: Function) -> Self {
+ Self::Function(func)
+ }
+
+ pub fn variable(name: impl Into<String>) -> Self {
+ Self::Variable(name.into())
+ }
+}
+
+/// Using Cow for Strings and Slices - Prototype Pattern
+pub struct VariableToken<'a> {
+ name: Cow<'a, str>,
+ value: f64,
+}
+
+impl<'a> VariableToken<'a> {
+ pub fn new(name: impl Into<Cow<'a, str>>, value: f64) -> Self {
+ Self {
+ name: name.into(),
+ value,
+ }
+ }
+}
diff --git a/design_patterns_in_rust/good_calculator_2/src/utilities.rs b/design_patterns_in_rust/good_calculator_2/src/utilities.rs
@@ -0,0 +1,176 @@
+//! Decorator pattern
+
+use std::{cell::RefCell, collections::HashMap, time::Instant};
+
+pub trait Expression {
+ fn evaluate(&self, variables: &HashMap<String, f64>) -> Result<f64, String>;
+ fn to_string(&self) -> String;
+ fn precedence(&self) -> u8 {
+ 0
+ }
+}
+
+// Leaf node for number values
+#[derive(Debug, Clone)]
+pub struct NumberExpression {
+ pub value: f64,
+}
+
+impl NumberExpression {
+ pub fn new(value: f64) -> Self {
+ Self { value }
+ }
+}
+
+impl Expression for NumberExpression {
+ fn evaluate(&self, _variables: &HashMap<String, f64>) -> Result<f64, String> {
+ Ok(self.value)
+ }
+
+ fn to_string(&self) -> String {
+ format!("{}", self.value)
+ }
+}
+
+/// Composites Pattern
+pub struct VariableExpression {
+ pub name: String,
+}
+
+impl Expression for VariableExpression {
+ fn evaluate(&self, variables: &HashMap<String, f64>) -> Result<f64, String> {
+ variables
+ .get(&self.name)
+ .copied()
+ .ok_or_else(|| format!("Undefined variable: {}", self.name))
+ }
+
+ fn to_string(&self) -> String {
+ self.name.clone()
+ }
+}
+
+/// Logging decorator
+///
+/// Notice that Logger is itself a trait object, which means we can swap in
+/// different logging backends (console, file, network) without changing
+/// the decorator
+pub trait Logger {
+ fn log(&self, message: &str);
+}
+
+pub struct ConsoleLogger;
+
+impl Logger for ConsoleLogger {
+ fn log(&self, message: &str) {
+ println!("[LOG] {}", message);
+ }
+}
+
+/// This holds both the expression it decorates, and the logger it uses,
+/// composing two independent abstractions
+pub struct LoggingExpression {
+ inner: Box<dyn Expression>,
+ logger: Box<dyn Logger>,
+}
+
+impl LoggingExpression {
+ pub fn new(inner: Box<dyn Expression>, logger: Box<dyn Logger>) -> Self {
+ Self { inner, logger }
+ }
+}
+
+impl Expression for LoggingExpression {
+ fn evaluate(&self, variables: &HashMap<String, f64>) -> Result<f64, String> {
+ self.logger
+ .log(&format!("Evaluating: {}", self.inner.to_string()));
+ let result = self.inner.evaluate(variables);
+ match &result {
+ Ok(val) => self.logger.log(&format!("Result: {}", val)),
+ Err(err) => self.logger.log(&format!("Error: {}", err)),
+ }
+ result
+ }
+
+ /// These delegation are critial, it makes the decorator transparent to any
+ /// code that doesn't care about logging
+ fn to_string(&self) -> String {
+ self.inner.to_string()
+ }
+ fn precedence(&self) -> u8 {
+ self.inner.precedence()
+ }
+}
+
+/// The timing decorator pattern
+pub struct TimingExpression {
+ inner: Box<dyn Expression>,
+}
+
+impl TimingExpression {
+ pub fn new(inner: Box<dyn Expression>) -> Self {
+ Self { inner }
+ }
+}
+
+/// The decorator captures the current time before delegating to the inner expression
+impl Expression for TimingExpression {
+ fn evaluate(&self, variables: &HashMap<String, f64>) -> Result<f64, String> {
+ let start = Instant::now();
+ let result = self.inner.evaluate(variables);
+ let duration = start.elapsed();
+ println!("Evaluation took: {:?}", duration);
+ result
+ }
+
+ /// These delegations are critial, it makes the decorator transparent to any
+ /// code that doesn't care about timing
+ fn to_string(&self) -> String {
+ self.inner.to_string()
+ }
+ fn precedence(&self) -> u8 {
+ self.inner.precedence()
+ }
+}
+
+/// The caching decorator pattern with interior mutability
+pub struct CachingExpression {
+ inner: Box<dyn Expression>,
+ // The RefCell<Option<f64>> allows us to mutate the cached value through
+ // a shared reference
+ last_result: RefCell<Option<f64>>,
+}
+
+impl CachingExpression {
+ pub fn new(inner: Box<dyn Expression>) -> Self {
+ Self {
+ inner,
+ last_result: RefCell::new(None),
+ }
+ }
+
+ pub fn invalidate_cache(&self) {
+ *self.last_result.borrow_mut() = None;
+ }
+}
+
+impl Expression for CachingExpression {
+ fn evaluate(&self, variables: &HashMap<String, f64>) -> Result<f64, String> {
+ // Check if the result already cached
+ if let Some(result) = *self.last_result.borrow() {
+ return Ok(result);
+ }
+ // Cache the result
+ let result = self.inner.evaluate(variables)?;
+ *self.last_result.borrow_mut() = Some(result);
+ Ok(result)
+ }
+
+ fn to_string(&self) -> String {
+ self.inner.to_string()
+ }
+
+ fn precedence(&self) -> u8 {
+ self.inner.precedence()
+ }
+}
diff --git a/design_patterns_in_rust/index.md b/design_patterns_in_rust/index.md
@@ -0,0 +1,84 @@
+---
+title: Design Patterns and Best Practices in Rust
+---
+
+Rust exercises following the book Design Patterns and Best Practices in Rust -
+Evan Williams
+
+Learning by doing a few bad examples and a few good examples.
+
+## Bad Examples with Anti-Patterns in Rust using OOP
+
+#### Use Box<dyn Operand> dynamic dispatch everywhere, end up writing a lot of code to force OOP class and interface polymorphism:
+
+> [!info] Bad Calculator - Source Code:
+> [exercises/design_patterns_in_rust/bad_calculator/src/main.rs](https://github.com/ling0x/notes/blob/main/content/exercises/design_patterns_in_rust/bad_calculator/src/main.rs)
+
+#### Leverage enums to perform the same functions but more concisely:
+
+> [!info] Not So Bad Calculator - Source Code:
+> [exercises/design_patterns_in_rust/not_so_bad_calculator/src/main.rs](https://github.com/ling0x/notes/blob/main/content/exercises/design_patterns_in_rust/not_so_bad_calculator/src/main.rs)
+
+#### Use OO constructors and store more data inside enums to make it more generic that resulted in silent errors:
+
+> [!info] Slightly Worse Calculator - Source Code:
+> [exercises/design_patterns_in_rust/slightly_worse_calculator/src/main.rs](https://github.com/ling0x/notes/blob/main/content/exercises/design_patterns_in_rust/slightly_worse_calculator/src/main.rs)
+
+#### Overuse enums and sub-enums to scale the code to more use cases end up with code that is very difficult to work with:
+
+> [!info] The Worst Calculator - Source Code:
+> [exercises/design_patterns_in_rust/the_worst_calculator/src/main.rs](https://github.com/ling0x/notes/blob/main/content/exercises/design_patterns_in_rust/the_worst_calculator/src/main.rs)
+
+### Examples for ownership and lifetime management
+
+The advantages the better example offers that the bad examples don't:
+
+- Clear ownership semantics: Each component should own its data directly, with
+ no unnecessary indirection
+- Explicit state management: The struct keeps related data together
+- Better error handling: Use Result types to handle errors explicitly
+- Thread-safety when needed: Instead of sprinkling Rc and RefCell throughout the
+ code, we provide a separate thread-safe wrapper when concurrent access is
+ required.
+- Simpler mental model: We can reason about data flow more easily because
+ ownership and mutation are explicit
+- Better performance: We avoid the overhead of reference counting and runtime
+ borrowing checks in the common case
+
+> [!info] Source Code:
+> [exercises/design_patterns_in_rust/bad_calculator_2/src/main.rs](https://github.com/ling0x/notes/blob/main/content/exercises/design_patterns_in_rust/bad_calculator_2/src/main.rs)
+
+### These examples demonstrate the anti-pattern of using `unsafe` and `global statics` to fight the borrow checker and how they can be refactored to work with Rust's ownership system.
+
+- Separate concerns by phase: parse first, then process, then store. Each phase
+ should complete before the next begins.
+- Use indices instead of references
+- Let data flow downward
+- Question whether you need interior mutability
+- Keep mutable state local
+- Trust the borrow checker's feedback
+
+> [!info] Source Code:
+> [exercises/design_patterns_in_rust/bad_calculator_3/src/main.rs](https://github.com/ling0x/notes/blob/main/content/exercises/design_patterns_in_rust/bad_calculator_3/src/main.rs)
+
+## The Gang of Four design patterns
+
+### Creational patterns
+
+- Abstract Factory
+- Builder Pattern
+- Prototype Pattern
+
+> [!info] Source Code:
+> [exercises/design_patterns_in_rust/good_calculator/src/main.rs](https://github.com/ling0x/notes/blob/main/content/exercises/design_patterns_in_rust/good_calculator/src/main.rs)
+
+### Structural patterns
+
+- Proxies, decorators, and adapters
+- Facades
+- Composites
+- Flyweight
+- The Bridge pattern
+
+> [!info] Source Code:
+> [exercises/design_patterns_in_rust/good_calculator_2/src/main.rs](https://github.com/ling0x/notes/blob/main/content/exercises/design_patterns_in_rust/good_calculator_2/src/main.rs)
diff --git a/design_patterns_in_rust/not_so_bad_calculator/Cargo.lock b/design_patterns_in_rust/not_so_bad_calculator/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "not_so_bad_calculator"
+version = "0.1.0"
diff --git a/design_patterns_in_rust/not_so_bad_calculator/Cargo.toml b/design_patterns_in_rust/not_so_bad_calculator/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "not_so_bad_calculator"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
diff --git a/design_patterns_in_rust/not_so_bad_calculator/src/main.rs b/design_patterns_in_rust/not_so_bad_calculator/src/main.rs
@@ -0,0 +1,104 @@
+//! Using enums where they don't make sense!
+//! We had so much difficulty and wrote so much code just to create
+//! one of these in the bad_calculator. Let's use enums this time
+//! for another bad example, albeit slightly better than the
+//! Box<dyn Operand> approach in the other bad example
+
+use std::{
+ io::{Write, stdin, stdout},
+ process::exit,
+};
+
+enum Operand {
+ Value(f64),
+}
+
+impl Operand {
+ fn evaluate(&self) -> f64 {
+ match self {
+ Operand::Value(v) => *v,
+ }
+ }
+}
+
+enum Operator {
+ Addition { lhs: Operand, rhs: Operand },
+ Subtraction { lhs: Operand, rhs: Operand },
+ Multiplication { lhs: Operand, rhs: Operand },
+ Division { lhs: Operand, rhs: Operand },
+ Negation { operand: Operand },
+}
+
+impl Operator {
+ fn apply(&self) -> Operand {
+ let inner = match self {
+ Operator::Addition { lhs, rhs } => lhs.evaluate() + rhs.evaluate(),
+ Operator::Subtraction { lhs, rhs } => lhs.evaluate() - rhs.evaluate(),
+ Operator::Multiplication { lhs, rhs } => lhs.evaluate() * rhs.evaluate(),
+ Operator::Division { lhs, rhs } => lhs.evaluate() / rhs.evaluate(),
+ Operator::Negation { operand } => -operand.evaluate(),
+ };
+ Operand::Value(inner)
+ }
+
+ fn precedence(&self) -> u8 {
+ match self {
+ Operator::Addition { .. } | Operator::Subtraction { .. } => 0,
+ Operator::Multiplication { .. } | Operator::Division { .. } => 1,
+ Operator::Negation { .. } => 2,
+ }
+ }
+
+ fn symbol(&self) -> char {
+ match self {
+ Operator::Addition { .. } => '+',
+ Operator::Subtraction { .. } => '-',
+ Operator::Multiplication { .. } => '*',
+ Operator::Division { .. } => '/',
+ Operator::Negation { .. } => '-',
+ }
+ }
+}
+
+fn evaluate_expression(expression: &str) -> Result<String, String> {
+ let addition = Operator::Addition {
+ lhs: Operand::Value(2.0),
+ rhs: Operand::Value(3.0),
+ };
+ let subtraction = Operator::Subtraction {
+ lhs: Operand::Value(5.0),
+ rhs: Operand::Value(1.0),
+ };
+ let negation = Operator::Negation {
+ operand: Operand::Value(-7.0),
+ };
+
+ println!("Addition result: {}", addition.apply().evaluate());
+ println!("Subtraction result: {}", subtraction.apply().evaluate());
+ println!("Negation result: {}", negation.apply().evaluate());
+
+ Ok("Finished".to_string())
+}
+
+/// This is a project to demonstrate BAD practices and anti-patterns
+/// in Rust in order to learn
+fn main() {
+ let mut buf = String::new();
+ loop {
+ print!("> ");
+
+ stdout().flush().unwrap();
+
+ buf.clear();
+ stdin().read_line(&mut buf).unwrap();
+
+ if buf.trim() == "exit" {
+ exit(0)
+ }
+
+ match evaluate_expression(&buf) {
+ Ok(result) => println!("{result}"),
+ Err(error) => println!("Error: {error}"),
+ }
+ }
+}
diff --git a/design_patterns_in_rust/slightly_worse_calculator/Cargo.lock b/design_patterns_in_rust/slightly_worse_calculator/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "slightly_worse_calculator"
+version = "0.1.0"
diff --git a/design_patterns_in_rust/slightly_worse_calculator/Cargo.toml b/design_patterns_in_rust/slightly_worse_calculator/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "slightly_worse_calculator"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
diff --git a/design_patterns_in_rust/slightly_worse_calculator/src/main.rs b/design_patterns_in_rust/slightly_worse_calculator/src/main.rs
@@ -0,0 +1,147 @@
+//! Using enums where they don't make sense!
+//! We can certainly overuse enums and make the code worse, by
+//! putting more states into each enum to make them store more data,
+//! and by using a string type match arms that can introduce errors
+//! silently, which the compiler won't even catch!
+
+use std::{
+ io::{Write, stdin, stdout},
+ process::exit,
+};
+
+enum Operand {
+ Value(f64),
+}
+
+impl Operand {
+ fn evaluate(&self) -> f64 {
+ match self {
+ Operand::Value(v) => *v,
+ }
+ }
+}
+
+enum Operator {
+ Addition {
+ lhs: Operand,
+ rhs: Operand,
+ precedence: u8,
+ symbol: char,
+ },
+ Subtraction {
+ lhs: Operand,
+ rhs: Operand,
+ precedence: u8,
+ symbol: char,
+ },
+ Multiplication {
+ lhs: Operand,
+ rhs: Operand,
+ precedence: u8,
+ symbol: char,
+ },
+ Division {
+ lhs: Operand,
+ rhs: Operand,
+ precedence: u8,
+ symbol: char,
+ },
+ Negation {
+ operand: Operand,
+ precedence: u8,
+ symbol: char,
+ },
+}
+
+impl Operator {
+ // Wrongly use Object Oriented constructor
+ fn new(operator_type: String, operand1: Operand, operand2: Option<Operand>) -> Self {
+ match operator_type.as_str() {
+ "add" => Operator::Addition {
+ lhs: operand1,
+ rhs: operand2.unwrap(),
+ precedence: 0,
+ symbol: '+',
+ },
+ // notice the typo here, the compile wont catch it!
+ "sub" => Operator::Addition {
+ lhs: operand1,
+ rhs: operand2.unwrap(),
+ precedence: 0,
+ symbol: '-',
+ },
+ // notice the typo here, the compile wont catch it!
+ "mul" => Operator::Division {
+ lhs: operand1,
+ rhs: operand2.unwrap(),
+ precedence: 1,
+ symbol: '*',
+ },
+ "div" => Operator::Division {
+ lhs: operand1,
+ rhs: operand2.unwrap(),
+ precedence: 1,
+ symbol: '/',
+ },
+ "neg" => Operator::Negation {
+ operand: operand1,
+ precedence: 2,
+ symbol: '-',
+ },
+ _ => panic!("Unknown operator"),
+ }
+ }
+
+ fn apply(&self) -> Operand {
+ let inner = match self {
+ Operator::Addition { lhs, rhs, .. } => lhs.evaluate() + rhs.evaluate(),
+ Operator::Subtraction { lhs, rhs, .. } => lhs.evaluate() - rhs.evaluate(),
+ Operator::Multiplication { lhs, rhs, .. } => lhs.evaluate() * rhs.evaluate(),
+ Operator::Division { lhs, rhs, .. } => lhs.evaluate() / rhs.evaluate(),
+ Operator::Negation { operand, .. } => -operand.evaluate(),
+ };
+ Operand::Value(inner)
+ }
+}
+
+fn evaluate_expression(expression: &str) -> Result<String, String> {
+ let addition = Operator::new(
+ "add".to_string(),
+ Operand::Value(2.0),
+ Some(Operand::Value(3.0)),
+ );
+ let subtraction = Operator::new(
+ "sub".to_string(),
+ Operand::Value(5.0),
+ Some(Operand::Value(1.0)),
+ );
+ let negation = Operator::new("neg".to_string(), Operand::Value(-7.0), None);
+
+ println!("Addition result: {}", addition.apply().evaluate());
+ println!("Subtraction result: {}", subtraction.apply().evaluate());
+ println!("Negation result: {}", negation.apply().evaluate());
+
+ Ok("Finished".to_string())
+}
+
+/// This is a project to demonstrate BAD practices in Rust in order to learn
+fn main() {
+ let mut buf = String::new();
+ loop {
+ print!("> ");
+
+ stdout().flush().unwrap();
+
+ buf.clear();
+ stdin().read_line(&mut buf).unwrap();
+
+ if buf.trim() == "exit" {
+ exit(0)
+ }
+
+ match evaluate_expression(&buf) {
+ Ok(result) => println!("{result}"),
+ Err(error) => println!("Error: {error}"),
+ }
+ }
+}
diff --git a/design_patterns_in_rust/the_worst_calculator/Cargo.lock b/design_patterns_in_rust/the_worst_calculator/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "the_worst_calculator"
+version = "0.1.0"
diff --git a/design_patterns_in_rust/the_worst_calculator/Cargo.toml b/design_patterns_in_rust/the_worst_calculator/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "the_worst_calculator"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
diff --git a/design_patterns_in_rust/the_worst_calculator/src/main.rs b/design_patterns_in_rust/the_worst_calculator/src/main.rs
@@ -0,0 +1,118 @@
+//! Overuse enums in rust makes it very difficult to reason about
+
+use std::{
+ io::{Write, stdin, stdout},
+ ops::Range,
+ process::exit,
+ time::{Duration, Instant},
+};
+
+enum Operand {
+ NumericValue(f64),
+ StringValue(String),
+ RangeValue(Range<usize>),
+ InstantValue(Instant),
+ DurationValue,
+}
+
+// Sub-enums of enums...
+//
+enum ArithmeticOperator {
+ Addition { lhs: f64, rhs: f64 },
+ Subtraction { lhs: f64, rhs: f64 },
+ // ...
+}
+
+enum TextOperator {
+ Concatenate {
+ lhs: String,
+ rhs: String,
+ },
+ SubString {
+ operand: String,
+ bounds: Range<usize>,
+ },
+ // ...
+}
+
+enum DateOperator {
+ AddDays { lhs: Instant, rhs: Duration },
+ SubtractDays { lhs: Instant, rhs: Instant },
+ // ...
+}
+
+enum Operator {
+ Arithmetic(ArithmeticOperator),
+ Text(TextOperator),
+ Date(DateOperator),
+}
+
+impl ArithmeticOperator {
+ fn apply(&self) -> Operand {
+ match self {
+ ArithmeticOperator::Addition { lhs, rhs } => todo!(),
+ ArithmeticOperator::Subtraction { lhs, rhs } => todo!(),
+ }
+ }
+}
+
+impl TextOperator {
+ fn apply(&self) -> Operand {
+ match self {
+ TextOperator::Concatenate { lhs, rhs } => todo!(),
+ TextOperator::SubString { operand, bounds } => todo!(),
+ }
+ }
+}
+
+impl DateOperator {
+ fn apply(&self) -> Operand {
+ match self {
+ DateOperator::AddDays { lhs, rhs } => todo!(),
+ DateOperator::SubtractDays { lhs, rhs } => todo!(),
+ }
+ }
+}
+
+/// Forcing OO hierarchy onto rust by using enums and sub-enums,
+/// apply() and sub apply(), even though this could work, we have
+/// made the relatively clean patterns very difficult to work with
+///
+/// Instead of using OO hierarchies, we should use rust modules to
+/// keep like types togther via the module system...
+impl Operator {
+ fn apply(&self) -> Operand {
+ match self {
+ Operator::Arithmetic(arithmetic_operator) => arithmetic_operator.apply(),
+ Operator::Text(text_operator) => text_operator.apply(),
+ Operator::Date(date_operator) => date_operator.apply(),
+ }
+ }
+}
+
+fn evaluate_expression(expression: &str) -> Result<Operand, Operand> {
+ todo!()
+}
+
+/// This is a project to demonstrate BAD practices and anti-patterns
+/// in Rust in order to learn
+fn main() {
+ // let mut buf = Operand::new();
+
+ // loop {
+ // print!("> ");
+ // std::io::stdout().flush().unwrap();
+
+ // buf.clear();
+ // std::io::stdin().read_line(&mut buf).unwrap();
+
+ // if buf.trim() == "exit" {
+ // exit(0)
+ // }
+
+ // match evaluate_expression(&buf) {
+ // Ok(result) => println!("{result}"),
+ // Err(error) => println!("Error: {error}"),
+ // }
+ // }
+}
diff --git a/index.md b/index.md
@@ -0,0 +1,5 @@
+---
+title: Exercises
+---
+
+Exercises in rust programming language for various software engineering topics
diff --git a/my_vec/Cargo.lock b/my_vec/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "my_vec"
+version = "0.1.0"
diff --git a/my_vec/Cargo.toml b/my_vec/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "my_vec"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
diff --git a/my_vec/index.md b/my_vec/index.md
@@ -0,0 +1,9 @@
+---
+title: "Exercise: My Vec"
+tags: [exercise, rust, vector]
+---
+
+A minimal vector built from scratch.
+
+> [!info] Source Code:
+> [exercises/my_vec](https://github.com/ling0x/notes/tree/main/content/exercises/my_vec)
diff --git a/my_vec/src/main.rs b/my_vec/src/main.rs
@@ -0,0 +1,35 @@
+// This project implements Vec from scratch (https://doc.rust-lang.org/nomicon/vec/vec.html)
+
+use std::{mem, ptr::NonNull};
+
+/// Layout:
+/// A Vec has three parts: a pointer to the allocation, the size of the allocation,
+/// and the number of elements that have been initialized.
+pub struct Vec<T> {
+ // NonNull is a wrapper around a raw pointer, which is covariant over T
+ // and is decalred to never be null.
+ ptr: NonNull<T>,
+ cap: usize,
+ len: usize,
+}
+
+// Vec<T> is Send/Sync if T is Send/Sync
+// (this produces the same results as using Unique<T>)
+unsafe impl<T: Send> Send for Vec<T> {}
+unsafe impl<T: Sync> Sync for Vec<T> {}
+
+impl<T> Vec<T> {
+ pub fn new() -> Self {
+ assert!(mem::size_of::<T>() != 0, "We're not ready to handle ZSTs");
+ Vec {
+ // Initialize values that lazily allocate like Vec::new does
+ ptr: NonNull::dangling(),
+ len: 0,
+ cap: 0,
+ }
+ }
+}
+
+fn main() {
+ println!("Hello, world!");
+}
diff --git a/spinlock/Cargo.lock b/spinlock/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "spinlock"
+version = "0.1.0"
diff --git a/spinlock/Cargo.toml b/spinlock/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "spinlock"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
diff --git a/spinlock/index.md b/spinlock/index.md
@@ -0,0 +1,11 @@
+---
+title: "Exercise: Spinlock"
+tags: [exercise, rust, concurrency, memory-safety]
+---
+
+A from-scratch implementation of a [spinlock](memory_safety/spinlock.md) in Rust
+using atomics and `UnsafeCell`. The goal is to understand how a mutex works at
+the lowest level — without relying on the OS scheduler.
+
+> [!info] Source Code:
+> [exercises/spinlock/src/main.rs](https://github.com/ling0x/notes/blob/main/content/exercises/spinlock/src/main.rs)
diff --git a/spinlock/src/main.rs b/spinlock/src/main.rs
@@ -0,0 +1,158 @@
+use std::{
+ cell::UnsafeCell,
+ hint::spin_loop,
+ ops::{Deref, DerefMut},
+ sync::atomic::{
+ AtomicBool,
+ Ordering::{Acquire, Relaxed, Release},
+ },
+ thread,
+};
+
+/// A Spinlock is the simplest possible implementation of a mutex, its general form looks like this
+/// ```rust
+/// static LOCKED: AtomicBool = AtomicBool::new(false);
+/// // 1. To grab a lock, we repeatedly execute compareandswap until it succeeds.
+/// // The CPU “spins” in this very short loop.
+/// while LOCKED.compare_and_swap(false, true, Ordering::Acquire) {
+/// // 4. Spinning is wasteful, so we use an intrinsic to instruct the CPU to
+/// // enter a low-power mode.
+/// std::sync::atomic::spin_loop_hint();
+/// }
+/// // 2. Only one thread at a time can be here.
+/// /* Critical section */
+/// // 3. To release the lock, we do a single atomic store.
+/// LOCKED.store(false, Ordering::Release);
+/// ```
+/// Checkout the "Spinlock Considered Harmful" post:
+/// https://matklad.github.io/2020/01/02/spinlocks-considered-harmful.html
+///
+/// All we need is a single boolean that indicates whether it is locked or not.
+pub struct SpinLock<T> {
+ /// QUESTION: What does an atomic bool does conceptually?
+ /// - Stores a true/false value that can be shared across threads without a mutex.
+ /// - Provides atomic operations like load, store, swap, compare_exchange,
+ /// and bitwise ops (fetch_or, fetch_and, fetch_not), each taking a memory
+ /// Ordering to control how operations are seen across threads.
+ /// - Uses CPU atomic instructions so an update is either fully seen or not seen at all by other threads; there is no partial write.
+ locked: AtomicBool,
+
+ /// We need to have an exclusive reference (&mut T) to the data protected by the lock
+ /// The value field holds the generic over the type of data the lock protects
+ /// We use UnsafeCell for interior mutability
+ value: UnsafeCell<T>,
+}
+
+/// A Safe Interface Using a Lock Guard
+///
+/// Wrap the reference in a type that implements the Drop trait to do something
+/// when it is dropped.
+///
+/// The existence of a Guard means that the SpinLock has been locked.
+pub struct Guard<'a, T> {
+ lock: &'a SpinLock<T>,
+}
+
+/// In order to make the UnsafeCell to be shareable between threads, we need to
+/// promise to the compiler that it is actually safe for our type to be shared
+/// between threads.
+unsafe impl<T> Sync for SpinLock<T> where T: Send {}
+
+/// Mutual Exclusion — the guarantee that only one thread can access the
+/// protected data at any given moment.
+///
+/// Spinlock Mechanism
+impl<T> SpinLock<T> {
+ pub const fn new(value: T) -> Self {
+ Self {
+ locked: AtomicBool::new(false),
+ value: UnsafeCell::new(value),
+ }
+ }
+
+ /// The lock method returns a Guard, such that the user isn't required to
+ /// write unsafe, unchecked code when using the lock to protect their data
+ pub fn lock(&self) -> Guard<'_, T> {
+ while self.locked.swap(true, Acquire) {
+ // Within the while loop, we use a spin loop hint, which emits a
+ // special CPU instruction that says “I’m in a tight busy‑wait loop;
+ // expect lots of repeated reads and no useful work.” This lets
+ // the core change how it treats that thread without involving
+ // the OS scheduler.
+ spin_loop();
+ }
+ Guard { lock: self }
+ }
+
+ /// We use acquire and release memory ordering to make sure that every
+ /// unlock() call establishes a happens-before relationship with the
+ /// lock() calls that follow.
+ ///
+ /// # Safety
+ ///
+ /// The &mut T from lock() must be gone!
+ /// (And no cheating by keeping reference to fields of that T around!)
+ pub unsafe fn unlock(&self) {
+ self.locked.store(false, Release);
+ }
+
+ /// use a compare-and-exchange operation to atomically check if the boolean
+ /// is false and set it to true if that’s the case
+ pub fn cas(&self) {
+ while self
+ .locked
+ .compare_exchange_weak(false, true, Acquire, Relaxed)
+ .is_err()
+ {
+ spin_loop();
+ }
+ }
+}
+
+/// To make Guard<T> behave like an (exclusive) reference, we have to implement
+/// the special Deref and DerefMut traits
+impl<T> Deref for Guard<'_, T> {
+ type Target = T;
+ fn deref(&self) -> &T {
+ // # Safety:
+ // The very existence of this Guard
+ // guarantees we've exclusively locked the lock.
+ unsafe { &*self.lock.value.get() }
+ }
+}
+
+impl<T> DerefMut for Guard<'_, T> {
+ fn deref_mut(&mut self) -> &mut T {
+ // # Safety:
+ // The very existence of this Guard
+ // guarantees we've exclusively locked the lock.
+ unsafe { &mut *self.lock.value.get() }
+ }
+}
+
+/// Add our own implementation of Send and Sync with the right bounds to make sure
+/// our Guard is only Sync if T is Sync (and Send if T is Send)
+unsafe impl<T> Send for Guard<'_, T> where T: Send {}
+unsafe impl<T> Sync for Guard<'_, T> where T: Sync {}
+
+/// Implement Drop for Guard, allowing us to the unsafe unlock method safe again
+impl<T> Drop for Guard<'_, T> {
+ fn drop(&mut self) {
+ self.lock.locked.store(false, Release);
+ }
+}
+
+fn main() {
+ let x = SpinLock::new(Vec::new());
+ thread::scope(|s| {
+ s.spawn(|| x.lock().push(1));
+ s.spawn(|| {
+ let mut g = x.lock();
+ g.push(2);
+ g.push(2);
+ });
+ });
+ let g = x.lock();
+ assert!(g.as_slice() == [1, 2, 2] || g.as_slice() == [2, 2, 1]);
+ println!("{:#?}", g.as_slice());
+}
diff --git a/tokio-tutorial/index.md b/tokio-tutorial/index.md
@@ -0,0 +1,11 @@
+---
+title: "Exercise: Tokio Tutorial — My Redis"
+tags: [exercise, rust, async, tokio, networking]
+---
+
+A minimal Redis-like server and client built while following the
+[Tokio tutorial](https://tokio.rs/tokio/tutorial). Covers async tasks, TCP
+sockets, shared state, and Tokio's channel primitives.
+
+> [!info] Source Code:
+> [exercises/tokio-tutorial/my-redis](https://github.com/ling0x/notes/tree/main/content/exercises/tokio-tutorial/my-redis)
diff --git a/tokio-tutorial/my-redis/Cargo.lock b/tokio-tutorial/my-redis/Cargo.lock
@@ -0,0 +1,1014 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "async-stream"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "atoi"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c897df197d57c25b37df9d8fa2f93ddbfeee9ebd2264350ac79c8ec4b795885"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
+
+[[package]]
+name = "bumpalo"
+version = "3.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
+
+[[package]]
+name = "bytes"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
+
+[[package]]
+name = "cc"
+version = "1.2.52"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "chrono"
+version = "0.4.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
+dependencies = [
+ "iana-time-zone",
+ "num-traits",
+ "windows-link",
+]
+
+[[package]]
+name = "clap"
+version = "2.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags 1.3.2",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41"
+
+[[package]]
+name = "futures-core"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+[[package]]
+name = "heck"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
+
+[[package]]
+name = "js-sys"
+version = "0.3.85"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libc"
+version = "0.2.180"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+
+[[package]]
+name = "matchers"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1"
+dependencies = [
+ "regex-automata 0.1.10",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
+
+[[package]]
+name = "mini-redis"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "528890a6b63c88c9bda173867128abe62c0f5050dd959f067aeea09e90439f33"
+dependencies = [
+ "async-stream",
+ "atoi",
+ "bytes",
+ "structopt",
+ "tokio",
+ "tokio-stream",
+ "tracing",
+ "tracing-futures",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "mio"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "my-redis"
+version = "0.1.0"
+dependencies = [
+ "bytes",
+ "mini-redis",
+ "tokio",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.105"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags 2.10.0",
+]
+
+[[package]]
+name = "regex"
+version = "1.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata 0.4.13",
+ "regex-syntax 0.8.8",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax 0.6.29",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax 0.8.8",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.149"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
+dependencies = [
+ "errno",
+ "libc",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "socket2"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
+dependencies = [
+ "libc",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "structopt"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10"
+dependencies = [
+ "clap",
+ "lazy_static",
+ "structopt-derive",
+]
+
+[[package]]
+name = "structopt-derive"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.114"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "tokio"
+version = "1.49.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-futures"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
+dependencies = [
+ "pin-project",
+ "tracing",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-serde"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1"
+dependencies = [
+ "serde",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71"
+dependencies = [
+ "ansi_term",
+ "chrono",
+ "lazy_static",
+ "matchers",
+ "regex",
+ "serde",
+ "serde_json",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+ "tracing-serde",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+
+[[package]]
+name = "valuable"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
+dependencies = [
+ "windows-link",
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
+
+[[package]]
+name = "zmij"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea"
diff --git a/tokio-tutorial/my-redis/Cargo.toml b/tokio-tutorial/my-redis/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "my-redis"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+tokio = { version = "1", features = ["full"] }
+bytes = "1"
+mini-redis = "0.4"
diff --git a/tokio-tutorial/my-redis/examples/hello-redis.rs b/tokio-tutorial/my-redis/examples/hello-redis.rs
@@ -0,0 +1,23 @@
+use mini_redis::{Result, client};
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ // Open a connection to the mini-redis address
+ //
+ // connect function asynchronously establishes a TCP connection with the
+ // specified remote address. Once the connection is established, a client
+ // handle is returned. Even though the operation is performed asynchronously,
+ // the code we write looks synchronous. The only indication that the operation
+ // is asynchronous is the .await operator.
+ let mut client = client::connect("127.0.0.1:6379").await?;
+
+ // Set the key "hello" with value "world"
+ client.set("hello", "world".into()).await?;
+
+ // Get key "hello"
+ let result = client.get("hello").await?;
+
+ print!("got value from the server; result={result:?}");
+
+ Ok(())
+}
diff --git a/tokio-tutorial/my-redis/index.md b/tokio-tutorial/my-redis/index.md
@@ -0,0 +1,76 @@
+---
+title: Tokio Tutorial
+---
+
+https://tokio.rs/tokio/tutorial
+
+## Four types of tokio channels:
+
+### oneshot channel
+
+The oneshot channel supports sending a single value from a single producer to a
+single consumer. This channel is usually used to send the result of a
+computation to a waiter.
+
+### mpsc channel
+
+The mpsc channel supports sending many values from many producers to a single
+consumer. This channel is often used to send work to a task or to receive the
+result of many computations.
+
+### broadcast channel
+
+The broadcast channel supports sending many values from many producers to many
+consumers. Each consumer will receive each value. This channel can be used to
+implement “fan out” style patterns common with pub / sub or “chat” systems.
+
+### watch channel
+
+The watch channel supports sending many values from many producers to many
+consumers. However, only the most recent value is stored in the channel.
+Consumers are notified when a new value is sent, but there is no guarantee that
+consumers will see all values.
+
+https://tokio.rs/tokio/tutorial/channels
+
+https://docs.rs/tokio/1.49.0/tokio/sync/
+
+## Concepts
+
+### Tokio Tasks
+
+Tasks are the unit of execution managed by the scheduler. Spawning the task
+submits it to the Tokio scheduler, which then ensures that the task executes
+when it has work to do. The spawned task may be executed on the same thread as
+where it was spawned, or it may execute on a different runtime thread. The task
+can also be moved between threads after being spawned.
+
+Tasks in Tokio are very lightweight. Under the hood, they require only a single
+allocation and 64 bytes of memory. Applications should feel free to spawn
+thousands, if not millions of tasks.
+
+https://tokio.rs/tokio/tutorial/spawning
+
+### Actors
+
+Nothing in the actor model requires that each actor is its own thread. To the
+contrary, most actor systems suggest that there should be a large number of
+actors, and so each actor should map to a task rather than a thread. After all,
+actors require exclusive access to their wrapped resources only when they
+execute, and do not care whether they are on a thread of their own or not. In
+fact, very frequently, the actor model is used in conjunction with the worker
+pool model—for example, an application that uses the multi- threaded
+asynchronous runtime Tokio can spawn an asynchronous task for each actor, and
+Tokio will then make the execution of each actor a job in its worker pool. Thus,
+the execution of a given actor may move from thread to thread in the worker pool
+as the actor yields and resumes, but every time the actor executes it maintains
+exclusive access to its wrapped resource.
+
+(Excerpt from Rust for Rustaceans)
+
+## Examples
+
+Beginner's Guide to Concurrent Programming: Coding a Multithreaded Chat Server
+using Tokio:
+
+https://github.com/pretzelhammer/rust-blog/blob/master/posts/chat-server.md
diff --git a/tokio-tutorial/my-redis/src/bin/client.rs b/tokio-tutorial/my-redis/src/bin/client.rs
@@ -0,0 +1,113 @@
+use bytes::Bytes;
+use mini_redis::client;
+use tokio::sync::{mpsc, oneshot};
+
+/// Multiple different commands are multiplexed over a single channel.
+#[derive(Debug)]
+enum Command {
+ Get {
+ key: String,
+ resp: Responder<Option<Bytes>>,
+ },
+ Set {
+ key: String,
+ val: Bytes,
+ resp: Responder<()>,
+ },
+}
+
+/// Provided by the requester and used by the manager task to send the command
+/// response back to the requester
+type Responder<T> = oneshot::Sender<mini_redis::Result<T>>;
+
+#[tokio::main]
+async fn main() {
+ // The mpsc channel supports sending many values from many producers to
+ // a single consumer
+ //
+ // Create a new channel with a capacity of at most 32.
+ // It returns two values: a sender and a receiver
+ //
+ // tx and rx:
+ // This naming comes from electronics and networking, where signal lines
+ // are often labeled TX (transmit) and RX (receive), and Rust’s channel
+ // examples follow the same tradition to indicate which end sends and which
+ // end receives.
+ let (tx, mut rx) = mpsc::channel(32);
+
+ // Sending from multiple tasks is done by cloning the Sender
+ let tx2 = tx.clone();
+
+ // Spawn a task that processes messages from the channel.
+ // First, a client connection is established to Redis.
+ // Then, received commands are issued via the Redis connection.
+ //
+ // The `move` keyword is used to **move** ownership of `rx` into the task.
+ let manager = tokio::spawn(async move {
+ // Establish a connection to the server
+ let mut client = client::connect("127.0.0.1:6379").await.unwrap();
+
+ // Start receiving messages
+ while let Some(cmd) = rx.recv().await {
+ match cmd {
+ Command::Get { key, resp } => {
+ let res = client.get(&key).await;
+
+ // Calling send on oneshot::Sender completes immediately
+ // and does not require an .await. This is because send on
+ // a oneshot channel will always fail or succeed immediately
+ // without any form of waiting.
+ let _ = resp.send(res);
+ }
+ Command::Set { key, val, resp } => {
+ let res = client.set(&key, val).await;
+
+ // Calling send on oneshot::Sender completes immediately
+ // and does not require an .await. This is because send on
+ // a oneshot channel will always fail or succeed immediately
+ // without any form of waiting.
+ let _ = resp.send(res);
+ }
+ }
+ }
+ });
+
+ // Both messages are sent to the single Receiver handle.
+ // It is not possible to clone the receiver of an mpsc channel.
+ let t1 = tokio::spawn(async move {
+ let (resp_tx, resp_rx) = oneshot::channel();
+ let cmd = Command::Get {
+ key: "foo".to_string(),
+ resp: resp_tx,
+ };
+
+ // Send the Get request
+ tx.send(cmd).await.unwrap();
+
+ // Await the response
+ let res = resp_rx.await;
+ println!("GOT = {:?}", res);
+ });
+
+ let t2 = tokio::spawn(async move {
+ let (resp_tx, resp_rx) = oneshot::channel();
+ let cmd = Command::Set {
+ key: "foo".to_string(),
+ val: "bar".into(),
+ resp: resp_tx,
+ };
+
+ // Send the SET request
+ tx2.send(cmd).await.unwrap();
+
+ // Await the response
+ let res = resp_rx.await;
+ println!("GOT = {:?}", res);
+ });
+
+ // At the bottom of the main function, we .await the join handles to ensure
+ // the commands fully complete before the process exits.
+ t1.await.unwrap();
+ t2.await.unwrap();
+ manager.await.unwrap();
+}
diff --git a/tokio-tutorial/my-redis/src/bin/server.rs b/tokio-tutorial/my-redis/src/bin/server.rs
@@ -0,0 +1,74 @@
+use bytes::Bytes;
+use mini_redis::{Connection, Frame};
+use std::collections::HashMap;
+use std::sync::{Arc, Mutex};
+use tokio::net::{TcpListener, TcpStream};
+
+// Using Arc allows the HashMap to be referenced concurrently from many tasks,
+// potentially running on many threads. Throughout Tokio, the term handle is
+// used to reference a value that provides access to some shared state.
+//
+// Use std::sync::Mutex instead of tokio::sync::Mutex
+type Db = Arc<Mutex<HashMap<String, Bytes>>>;
+
+#[tokio::main]
+async fn main() {
+ // Bind the listener to the address
+ let listener = TcpListener::bind("127.0.0.1:6379").await.unwrap();
+
+ println!("Listening");
+
+ // The HashMap will be shared across many tasks and potentially many threads.
+ // To support this, it is wrapped in Arc<Mutex<_>>
+ let db = Arc::new(Mutex::new(HashMap::new()));
+
+ loop {
+ // The second item contains the IP and port of the new connection.
+ let (socket, _) = listener.accept().await.unwrap();
+
+ // Clone the handle to the hash map
+ let db = db.clone();
+
+ println!("Accepted");
+
+ // A new task is spawned for each inbound socket. The socket is
+ // moved to the new task and processed there.
+ tokio::spawn(async move {
+ process(socket, db).await;
+ });
+ }
+}
+
+/// Process function handles incoming commands. It uses a HashMap to store values.
+/// SET commands will insert into the HashMap and GET values will load them.
+/// Additionally, we will use a loop to accept more than one command per connection.
+async fn process(socket: TcpStream, db: Db) {
+ use mini_redis::Command::{self, Get, Set};
+
+ // Connection, provided by `mini-redis`, handles parsing frames from the socket
+ let mut connection = Connection::new(socket);
+
+ // Use `read_frame` to receive a command from the connection.
+ while let Some(frame) = connection.read_frame().await.unwrap() {
+ let response = match Command::from_frame(frame).unwrap() {
+ Set(cmd) => {
+ let mut db = db.lock().unwrap();
+ // The value is stored as Vec<u8>
+ db.insert(cmd.key().to_string(), cmd.value().clone());
+ Frame::Simple("OK".to_string())
+ }
+ Get(cmd) => {
+ let db = db.lock().unwrap();
+ if let Some(value) = db.get(cmd.key()) {
+ Frame::Bulk(value.clone())
+ } else {
+ Frame::Null
+ }
+ }
+ cmd => panic!("unimplemented {cmd:?}"),
+ };
+
+ // Write the response to the client
+ connection.write_frame(&response).await.unwrap();
+ }
+}
diff --git a/tokio-tutorial/my-redis/src/connection.rs b/tokio-tutorial/my-redis/src/connection.rs
@@ -0,0 +1,132 @@
+use bytes::{Buf, BytesMut};
+use mini_redis::{Frame, Result, frame::Error::Incomplete};
+use std::io::Cursor;
+use tokio::{
+ io::{self, AsyncReadExt, AsyncWriteExt, BufWriter},
+ net::TcpStream,
+};
+
+pub struct Connection {
+ stream: BufWriter<TcpStream>,
+ buffer: BytesMut,
+}
+
+impl Connection {
+ pub fn new(stream: TcpStream) -> Connection {
+ Connection {
+ stream: BufWriter::new(stream),
+ // Allocate the buffer with 4kb of capacity.
+ buffer: BytesMut::with_capacity(4096),
+ }
+ }
+
+ /// Read a frame from the connection
+ ///
+ /// Returns `None` if EOF is reached
+ pub async fn read_frame(&mut self) -> Result<Option<Frame>> {
+ loop {
+ // Attempt to parse a frame from the buffered data. If
+ // enough data has been buffered, the frame is
+ // returned.
+ if let Some(frame) = self.parse_frame()? {
+ return Ok(Some(frame));
+ }
+
+ // There is not enough buffered data to read a frame.
+ // Attempt to read more data from the socket.
+ //
+ // On success, the number of bytes is returned. `0`
+ // indicates "end of stream".
+ if 0 == self.stream.read_buf(&mut self.buffer).await? {
+ // The remote closed the connection. For this to be
+ // a clean shutdown, there should be no data in the
+ // read buffer. If there is, this means that the
+ // peer closed the socket while sending a frame.
+ if self.buffer.is_empty() {
+ return Ok(None);
+ } else {
+ return Err("connection reset by peer".into());
+ }
+ }
+ }
+ }
+
+ fn parse_frame(&mut self) -> Result<Option<Frame>> {
+ // Create the `T: Buf` type
+ let mut buf = Cursor::new(&self.buffer[..]);
+
+ // Check whether a full frame is available
+ match Frame::check(&mut buf) {
+ Ok(_) => {
+ // Get the bytes length of the frame
+ let len = buf.position() as usize;
+
+ // Reset the internal cursor for the call to `parse`.
+ buf.set_position(0);
+
+ // Parse the frame
+ let frame = Frame::parse(&mut buf)?;
+
+ // Discard the frame from the buffer
+ self.buffer.advance(len);
+
+ // Return the frame to the caller
+ Ok(Some(frame))
+ }
+ Err(Incomplete) => Ok(None),
+ Err(e) => Err(e.into()),
+ }
+ }
+
+ /// Write a frame to the connection
+ pub async fn write_frame(&mut self, frame: &Frame) -> io::Result<()> {
+ match frame {
+ Frame::Simple(val) => {
+ self.stream.write_u8(b'+').await?;
+ self.stream.write_all(val.as_bytes()).await?;
+ self.stream.write_all(b"\r\n").await?;
+ }
+ Frame::Error(val) => {
+ self.stream.write_u8(b'-').await?;
+ self.stream.write_all(val.as_bytes()).await?;
+ self.stream.write_all(b"\r\n").await?;
+ }
+ Frame::Integer(val) => {
+ self.stream.write_u8(b':').await?;
+ self.write_decimal(*val).await?;
+ }
+ Frame::Bulk(val) => {
+ let len = val.len();
+
+ self.stream.write_u8(b'$').await?;
+ self.write_decimal(len as u64).await?;
+ self.stream.write_all(val).await?;
+ self.stream.write_all(b"\r\n").await?;
+ }
+ Frame::Null => {
+ self.stream.write_all(b"$-1\r\n").await?;
+ }
+ Frame::Array(frames) => unimplemented!(),
+ }
+
+ self.stream.flush().await;
+
+ Ok(())
+ }
+
+ /// Write a decimal frame to the stream
+ async fn write_decimal(&mut self, val: u64) -> io::Result<()> {
+ use std::io::Write;
+
+ // Convert the value to a string
+ let mut buf = [0u8; 12];
+ let mut buf = Cursor::new(&mut buf[..]);
+ write!(&mut buf, "{}", val)?;
+
+ let pos = buf.position() as usize;
+ self.stream.write_all(&buf.get_ref()[..pos]).await?;
+ self.stream.write_all(b"\r\n").await?;
+
+ Ok(())
+ }
+}
diff --git a/tokio-tutorial/my-redis/src/frame.rs b/tokio-tutorial/my-redis/src/frame.rs
diff --git a/tokio-tutorial/my-redis/src/lib.rs b/tokio-tutorial/my-redis/src/lib.rs
@@ -0,0 +1,2 @@
+pub mod connection;
+pub mod frame;