Tuesday, April 9, 2024

Observer pattern in Rust - driven by intrinsic motivation...


Work is worship...

 The observer design pattern is a very popular design pattern in the Object Oriented world. I must admit, I first saw the usefulness of this design pattern while studying the document view architecture of the MFC source code.

Later on, I used this pattern in many places.

There is a lot of similarity between the Observer Pattern, the Callback mechanism, and the Event Handler pattern in Java. Usually, the callback method is used when there is only one observer who awaits the signal from the subject.

So, let me put it in this fashion.

Suppose, there is a central document that is viewed by a few applications - someone is viewing it in a spreadsheet, someone as a Pie chart, and so on.

Now if the data in the document is updated, all the viewers must be updated and they should synchronise their views with the latest data set. So basically all the viewers were observing the central data. The moment it changes, all the observers get their respective views updated.

The class diagram and the sequence diagram of the observer pattern will be as follows.


Class Diagram




Sequence Diagram

Here goes an example of Observer Pattern written in Rust.


trait Observer {

fn update(&self,data:&str);

}


struct Subject<'a> {

observers: Vec<&'a dyn Observer>,

state: String,

}


impl<'a> Subject<'a> {

fn new(state: String) -> Self {

Self {

observers: Vec::new(),

state: state,

}

}


fn attach(&mut self, observer: &'a dyn Observer) {

self.observers.push(observer);

}


fn detach(&mut self, observer: &dyn Observer) {

self.observers.retain(|o| !std::ptr::eq(*o, observer));

}


fn notify(&self) {

for o in &self.observers {

o.update(&self.state);

}

}


fn set_state(&mut self, state: String) {

self.state = state;

self.notify();

}

}


struct ConcreteObserver {

name: String,

}




impl Observer for ConcreteObserver {

fn update(&self,data:&str) {

println!("{} received data: {}",self.name,data);

}

}



fn main() {{}

let mut subject = Subject::new("initial data".to_string());


let observer1=ConcreteObserver {

name: "Observer 1".to_string(),

};


let observer2=ConcreteObserver {

name: "Observer 2".to_string(),

};



subject.attach(&observer1);

subject.attach(&observer2);



subject.set_state("updated_data".to_string());


subject.detach(&observer2);


subject.set_state("Again updated data".to_string());


subject.detach(&observer1);

}



Friday, March 29, 2024

Adapter pattern in Rust - my exploration continues - in Rust, I keep my Trust...

 

It's truly said that if you teach a person, actually two people learn.

As a guru of my young son, Ridit, I taught him many design patterns and he implemented them in three different languages.

Here's his discussion on Adaptor Design Pattern.

Please go through his explanation.




Today I implemented his work of Adapter Pattern using Rust.

In Rust... I keep my Trust.

Rust addresses memory safety and concurrency issues that plague other systems languages like C and C++. This makes it attractive for building reliable, high-performance systems.

Rust is already being used in embedded systems, operating system kernels, and high-performance computing. Rust's memory safety makes it ideal for applications where security is paramount.

In simple words, the future of Rust programming language looks bright.

Here's the source code for the Adapter Design Pattern in Rust.

use std::io;


trait IWeatherFinder {

fn get_temperature(&self, city_name : &str)-> i32;

}


struct WeatherFinder{}

impl IWeatherFinder for WeatherFinder{

fn get_temperature(&self, city_name : &str) -> i32{

if (city_name.trim().eq("Kolkata".trim())){

40

}

else{

println!("Unknown City Name...Could not read temperature");

-273

}

}

}


trait iWeatherFinderClient {

fn get_temperature (&self, city_pincode : i32)->i32;

}


struct WeatherAdapter{}

impl WeatherAdapter {

fn get_city_name (&self, pincode : i32) -> &str {

if pincode == 700078 {

"Kolkata".trim()

}

else {

"UnknownCity"

}

}

fn get_temperature (&self, pincode : i32)-> i32 {

let city_name = self.get_city_name(pincode);

let weatherfinder : WeatherFinder = *Box::new(WeatherFinder{});

weatherfinder.get_temperature(city_name)

}

}


fn main() {

println!("Enter pin code");

let mut pincode = String::new();

io::stdin().read_line(&mut pincode).expect("Failed to read line");

let pin_code: i32 = pincode.trim().parse().expect("Input not an integer");

let weatheradapter = WeatherAdapter{};

let temperature: i32 = weatheradapter.get_temperature(pin_code);

println!("The temparature is {} degree celcius", temperature);

}


Output:

Enter pin code

700078

The temperature is 40 degree celcius

Thursday, March 28, 2024

Strategy Design Pattern in Rust...


Karmyog - Work is worship...

In Rust, i keep my Trust...

The strategy design pattern is a behavioral design pattern that lets you dynamically switch the behavior of an object at runtime. It achieves this by separating the core functionality of the object from the specific algorithms it uses.

Here's a breakdown of the core concepts:

Strategy Interface: This interface defines the common operation that all the different algorithms will implement. This ensures that all the interchangeable strategies can be used by the context object.

Concrete Strategies: These are the classes that implement the specific algorithms. Each concrete strategy class implements the strategy interface and provides its own unique behavior for the operation.

Context Object: This object holds a reference to a strategy object and delegates the specific operation to it. It can change its behavior at runtime by switching the reference to a different concrete strategy.

Here's an example of an UML diagram for the Strategy Design Pattern.



In my code, the 

trait TransportationToAirport{

fn going_to_the_airport(&self);

}

plays the role of the Strategy interface.

Three concrete Strategy classes have been derived from this interface  - namely, By_Ola, By_Bus and By_Rapido.

These concrete strategy classes help to pick up a specific way for going to the airport dynamically, i.e.,  in runtime.

Here's the source code for Strategy Pattern implemented in Rust.

use std::io;


trait TransportationToAirport{

fn going_to_the_airport(&self);

}


struct By_Bus{}


impl TransportationToAirport for By_Bus {

fn going_to_the_airport(&self) {

println!("Going to airport by Bus...");

}

}


struct By_Ola{}



impl TransportationToAirport for By_Ola{

fn going_to_the_airport(&self) {

println!("Going to airport by Ola...")

}

}


struct By_Rapido{}


impl TransportationToAirport for By_Rapido{

fn going_to_the_airport(&self) {

println!("Going to airport by Rapido...");

}

}



struct Traveller{

strategy : Box<dyn TransportationToAirport>,

}


impl Traveller{

fn new(strategy: Box<dyn TransportationToAirport>) -> Self {

Traveller { strategy }

}

fn travel(&self){

self.strategy.going_to_the_airport();

}

pub fn set_strategy(&mut self, strategy: Box<dyn TransportationToAirport>) {

self.strategy = strategy;

}

}



fn main() {

println!("Enter your choice...");

let mut choice = String::new();

io::stdin().read_line(&mut choice);

if choice.trim().eq("BUS".trim()){

let traveller : Traveller = Traveller::new(Box::new(By_Bus{}));

traveller.travel();

}

if choice.trim().eq("OLA".trim()){

let traveller : Traveller = Traveller::new(Box::new(By_Ola{}));

traveller.travel();

}

if choice.trim().eq("RAPIDO".trim()){

let traveller : Traveller = Traveller::new(Box::new(By_Rapido{}));

traveller.travel();

}

}

Here's the output of the above code:

Enter your choice...

OLA

Going to airport by Ola...

Monday, March 25, 2024

Proxy design pattern in Rust - my exploration continues - Karmyog is the best way forward...



Proxy pattern - as the name suggests - creates a proxy in place of a real heavy-duty object.

Let me give you a real-life example taken from computer science.

In case a document contains many huge-sized images, it does not load all the images when the doc gets loaded into the memory. Because it might take a very long time. 

The proxy pattern comes as a rescue. 

The document, instead of the actual mega images, gets loaded with very lightweight proxies of those images. And then when needed - in actual run time, i.e., when we need to see an image, the images get loaded by the proxy. This kind of proxy is called a virtual proxy.

Now let us talk from our example.

We are a family of three. Now my son does all the lightweight jobs - like, if there is a guest, he opens the door. So, he is the initial interface for the guests. However, in case, a guest wants to have lunch or dinner, my son calls his Mamma - because it's a heavy-duty job that he himself cannot do.

So basically my son gives proxy to his Mom, and if needed - like when he has to perform a heavy-duty job like cooking - he simply delegates the task to his Mom. For all other lightweight jobs, his Mom, who obviously has a lot of significant jobs to perform, remains in the background. She comes in the foreground in case there is a heavy-duty task like cooking for a guest.

Here's the UML class diagram of the proxy pattern.



Now the source code of Proxy Pattern implemented in Rust

Source Code

trait Family{

fn cook(&self);

fn open_the_door(&self){

println!("Son will handle Open The Door task");

}

}


struct Mamma{}


impl Family for Mamma {

fn cook(&self){

println!("Mamma is an expert cook..Mamma is cooking the food...");

}

}


struct Son<'a > {

mamma : & 'a Mamma,

}


impl <'a> Son<'a > {

fn new (mamma : & 'a Mamma)-> Son {

Son { mamma }

}

}


impl <'a> Family for Son<'a> {

fn cook(&self) {

println!("Son cannot cook.... So he is passing the buck to Mamma");

self.mamma.cook();

}

}


fn main() {

    let mamma : Mamma = Mamma { };

let son = Son :: new(&mamma);

son.open_the_door();

son.cook();

}


If we run the above code, the output will be like this:


Son will handle Open The Door task

Son cannot cook.... So he is passing the buck to Mamma

Mamma is an expert cook..Mamma is cooking the food...


Friday, March 22, 2024

Factory Design Pattern in Rust - my exploration continues...


Living a purposeful life - Karmyog... Salvation... Awakening...

You know, the best way to learn a modern computer programming language is to apply the nuances in designing a real life problem. This way I learned C++, Java and Python. And now I am applying the same logic while picking up Rust.

in Rust I keep my trust...

So...

here we go...

A simple factory design pattern in Rust - my second program of Rust.

Enjoy...

Source Code:

trait Food{

fn display(&self);

}


enum FoodType{

Chocolate,

Biscuit,

}


struct Chocolate{}


impl Food for Chocolate {

fn display(&self) {

println!("A chocolate is made...");

}

}


struct Biscuit{}

impl Food for Biscuit {

fn display(&self){

println!("A biscuit is made...");

}

}



struct FoodFactory;

impl FoodFactory{

fn new_food (item:&FoodType) -> Box<dyn Food>{

match item {

FoodType::Chocolate => Box::new(Chocolate{}),

FoodType::Biscuit => Box ::new(Biscuit{}),

}

}

}

fn main() {

let food = FoodFactory::new_food(&FoodType::Chocolate);

food.display();

let food = FoodFactory::new_food(&FoodType::Biscuit);

food.display();

}

Thursday, March 21, 2024

My first program using Rust - delving into trait...


चरैवेति, चरैवेति - Charaiveti, Charaiveti - keep walking...

because the motion is the life - we need to move on to keep the balance of life...


We must not stop.


The movement is essential.


Life is like a bicycle


The moment it stops - the balance goes for a toss


Life is like a stream.


The moment the flow of the stream is lost, all sorts of fungi pollute the water - and the water becomes dirty and ceases to be a potable one.


So, we have to keep moving all throughout our lives - physically - spiritually - and metaphorically - we must not stop the movement - because the


Ultimate Stop comes with Death.


So, here we go...


After C++, Java and Python...


my first program in Rust delving into trait - which is like an interface in Java or an abstract class of C++...


In Rust.... I trust....


Source Code:


trait Animal {

fn make_sound(&self);

fn wag_tail(&self){

println!("I don't have a tail...");

}

}


struct Human{}

impl Animal for Human {

fn make_sound(&self) {

println!("Human is speaking...");

}

}


struct Dog {}


impl Animal for Dog {

fn make_sound(&self) {

println!("Dog barks...");

}

fn wag_tail(&self) {

println!("The dog is waging it's tail");

}

}


fn main() {

let dog = Box::new (Dog {});

dog.make_sound();

dog.wag_tail();

let man = Box::new (Human {});

man.make_sound();

man.wag_tail();

}


The output:


Dog barks...

The dog is waging it's tail

Human is speaking...

I don't have a tail...

Wednesday, March 20, 2024

The goal of a software developer must be to move far above the language syntax - a study of Rust's Ownership Model vs C++ shared_ptr and unique_ptr


 

I always say to my student community that the primary goal of a software engineer must be to move above the language syntax and the nittie gritties of a software language. We must look at the code more from a designers' perspective and less as a programmers' perspective.

As I am trying to pick up Rust programming language to train my students, one thing I noticed and delved into was the ownership model of the Rust language and I could easily map it with the C++ shared_ptr and unique_ptr implementation.

My son Ridit and I conducted a study on the move copy of C++ shared_ptr long time back and then we wrote this blog post.



The move copy assignment operator is a member function of the std::shared_ptr class in C++. It is used to assign the ownership of a shared pointer to another shared pointer. The move copy assignment operator is more efficient than the copy assignment operator because it does not need to create a new copy of the underlying object.
The move copy assignment operator takes a single argument, which is a reference to a shared pointer. The argument must be an rvalue, which means that it cannot be a reference to a named variable. The move copy assignment operator then transfers the ownership of the underlying object from the argument to the shared pointer that it is called on.

The move copy assignment operator is typically used when the shared pointer that it is called on is about to be destroyed. This allows the ownership of the underlying object to be transferred to another shared pointer, which prevents the object from being deleted.

Here is an example of how to use the move copy assignment operator:

std::shared_ptr<int> ptr1 (new int(10));
std::shared_ptr<int> ptr2;

ptr2 = std::move(ptr1);

std::cout << *ptr2 << std::endl; // prints 10

In this example, the move copy assignment operator is used to transfer the ownership of the underlying object from ptr1 to ptr2. After the move copy assignment operator is called, ptr1 will no longer be a valid shared pointer.

Here's a comparison between Rust's ownership model and C++ shared_ptr /unique_ptr model.

Rust's ownership model

It is a core concept that governs memory management in the language. Unlike some languages that rely on garbage collection, Rust uses a system with strict rules enforced by the compiler at compile time. This ensures memory safety and prevents issues like memory leaks or dangling pointers.

Here's a breakdown of the key aspects of Rust's ownership model:

  • Ownership: Every value in Rust has an associated variable that acts as its owner. This variable is responsible for the value's lifetime in memory.
  • Moves: When you assign a value to another variable, ownership is transferred (moved). The original variable can no longer be used after the move. This prevents data corruption and ensures clarity about who "owns" the data.
  • Borrows: In some situations, you might need to use data without taking ownership. Rust allows borrowing, where a reference is created to access the data owned by another variable. The borrow checker, a part of the compiler, ensures borrows are valid and don't violate ownership rules.
  • Lifetimes: Lifetimes are annotations that specify the lifetime of references. They help the borrow checker guarantee that borrowed data is still valid when it's used.

By understanding these concepts, you can write memory-safe and efficient Rust programs. The ownership model might seem to be complex at first, but it becomes intuitive with practice and leads to robust and reliable software.

C++'s Smart Pointers (unique_ptr and shared_ptr):

  • Manual memory management with control: You have more control over ownership transfer and lifetime management.
  • Unique_ptr: Ensures single ownership and automatic deallocation when it goes out of scope. Similar to moving semantics in Rust, but managed at runtime.
  • Shared_ptr: Allows multiple owners to share a resource. Requires manual memory management and can lead to dangling pointers if not used carefully.
  • More complex and error-prone: Requires careful handling of ownership and potential for memory leaks or dangling pointers if not used correctly.

So the main point for the student community is to pick up one Object Oriented language very thoroughly and then you will find similarities in other languages - you will be able to map it with your existing knowledge base.

So... move way above the language syntax and get the big picture like an expert.