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++...


The cornerstone of abstraction in Rust is traits:

  • Traits are Rust's sole notion of interface. A trait can be implemented by multiple types, and in fact new traits can provide implementations for existing types. 

  • Traits can be statically dispatched. Like C++ templates, you can have the compiler generate a separate copy of an abstraction for each way it is instantiated. Static dispatch generally results in faster code execution because there is no overhead associated with determining which function to call at runtime. The trade-off is that the code footprint will be larger.

Here is an example of static dispatch.

use std::any::type_name;

trait Shape {
fn area(&self) -> f64;
}

struct Circle {
radius: f64,
}

struct Square {
side: f64,
}

impl Shape for Circle {
fn area(&self) -> f64 {
3.14 * self.radius * self.radius
}
}

impl Shape for Square {
fn area(&self) -> f64 {
self.side * self.side
}
}

fn print_area<T: Shape>(shape: T) {
println!("Area of : {} is {} :", type_name::<T>(), shape.area());
}

fn main() {
let c = Circle { radius: 3.0 };
let s = Square { side: 2.0 };

print_area(c); // Statically dispatched to Circle's implementation of `area`
print_area(s); // Statically dispatched to Square's implementation of `area`
}

  • Traits can be dynamically dispatched. Sometimes you really do need an indirection, and so it doesn't make sense to "erase" an abstraction at runtime. The same notion of interface -- the trait -- can also be used when you want to dispatch at runtime.

Source Code of dynamic dispatch:


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.

Friday, March 1, 2024

C++ 20 - concurrency - stop_token in jthread - politely interrupting the thread...

 When I studied the Android's Asynctask mechanism many years ago, I was fascinated about the implementation of a nice state machine for different stages of an asynchronous task.

C++ multi threading was a bit raw, in the sense that it was not matured in the initial stage. It seems it is catching up.

So let's continue - just for fun...



A thread cannot be preempted while in the critical section. But what about in case the thread is not inside a critical section and the thread function in the background is doing a heavy duty task like downloading a large video files from a remote server which you want to cancel from the UI (the reason for such a system is when the network is slow, the download may take a very long time and hence you want to cancel it midway). 

In C++ 20, the stop_token header which has introduced a mechanism by which any thread (obviously not in the critical section) can be interrupted and made exit the thread function. The stop_token header is a nice way of implementing the co-operative thread preemption and cancelling an asynctask midway.

Here's a brief description of it...

std::stop_token:

  • Represents the cancellation state of an asynchronous task.
  • Doesn't directly control the task, but indicates if a cancellation request has been made.
  • Used to check if cancellation is requested within the running task.

std::stop_callback

  • An optional callback function registered with a stop_token.
  • Invoked when the associated task is stopped.

Here in the example, the main thread goes to sleep for certain time. In that time frame, the worker thread continues to do the background task.

But after the main thread awakes from the sleeping mode, it requests the worker thread to stop and exit.

Here we go with the source code

The thread function is written as


void printIncrementingValues(std::stop_token stopToken) {

while (!stopToken.stop_requested()) {

std::cout << startNumber++ << " " << std::flush;

std::this_thread::sleep_for(500ms);

}

std::cout << std::endl;

}


Look at the code line in the above code block.

while (!stopToken.stop_requested())

This line is the soul of the whole concept as this keeps listening to the event when the stop_request is issued.

The complete source code is here.


/*

* Worker.h

*

* Created on: Mar 2, 2024

* Author: som

*/


#ifndef WORKER_H_

#define WORKER_H_


#include <iostream>

#include <stop_token>

#include <thread>


using namespace std::literals::chrono_literals;

using namespace std;




class Worker {

private:

int startNumber;

public:

Worker(int start){

startNumber = start;

}

virtual ~Worker(){


}


void printIncrementingValues(std::stop_token stopToken) {

while (!stopToken.stop_requested()) {

std::cout << startNumber++ << " " << std::flush;

std::this_thread::sleep_for(500ms);

}

std::cout << std::endl;

}

};


#endif /* WORKER_H_ */




//============================================================================

// Name : advanced_stop_token.cpp

// Author : som

// Version :

// Copyright : som-itsolutions

// Description : Hello World in C++, Ansi-style

//============================================================================


#include <iostream>

#include <stop_token>

#include <thread>

#include "Worker.h"


int main() {


Worker worker(1);

std::stop_source stopSource;

// creating stop_token object

std::stop_token stopToken = stopSource.get_token();


std::jthread ridit(&Worker::printIncrementingValues, &worker, stopToken);


// Register a callback


std::stop_callback callback(stopToken, []() {


std::cout << std::endl<<"Callback executed" << std::endl;


});


std::this_thread::sleep_for(10s);


stopSource.request_stop();


return 0;

}


And if we run this program, it will show the following

result.


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Callback executed


As the main thread is in the sleep mode for 10 seconds and the

interval for printing the numbers is set to 500 ms, hence in the

output there will be 1 to 20.


The new C++ is changing very fast - keeping yourself abreast

with the changes is of utmost importance for the engineers who

are using modern C++.


Friday, February 16, 2024

My C++ exploration - concurrency - async and future - driven by the Intrinsic Motivation...



My whole professional life in the world of software was driven by Intrinsic Motivation.

Intrinsic motivation is the act of doing something without any obvious external rewards. You do it because it's enjoyable and interesting, rather than because of an outside incentive or pressure to do it, such as a reward or deadline.

An example of intrinsic motivation for a software engineer would be deciphering a piece of a framework code because you enjoy doing it and have an interest in the matter or subject, rather than doing it because you have to prove a point to others or pass an exam.

Intrinsic motivation comes from within, while extrinsic motivation arises from outside. When you’re intrinsically motivated, you engage in an activity solely because you enjoy it and get personal satisfaction from it.

Let's come back to the nitty gritty of C++.

Here's my exploration of the C++ async method from the future header.

C++ async is a function template from the <future> header that helps you execute functions asynchronously, potentially in separate threads. It returns a std::future object that you can use to track the progress and retrieve the result of the asynchronous task.

Key Concepts:

Asynchronous Execution: async launches the provided function in a different thread (or using other mechanisms) without blocking the calling thread. This allows your program to continue doing other work while the asynchronous task is running.

std::future: The std::future object returned by async serves as a placeholder for the result of the asynchronous task. You can use methods like get(), wait(), valid(), and ready() to manage and access the result.

Common Use Cases:

Performing I/O-bound operations (e.g., network requests, file reading/writing) without blocking the main thread.

Source Code:

/*


* Callback.h


*


* Created on: 28-Dec-2023


* Author: ridit


*/


#ifndef CALLBACK_H_

#define CALLBACK_H_


class CallBack {


public:


virtual void onStartTask() = 0;


virtual void onFinishTask() = 0;


};


#endif /* CALLBACK_H_ */


/*

* Caller.h

*

* Created on: Feb 15, 2024

* Author: som

*/


#ifndef CALLER_H_

#define CALLER_H_


#include <iostream>

#include "Callee.h"


using namespace std;


//forward declaration

class Callee;


class Caller : public CallBack{


private:


Callee* callee;


public:


Caller(){


    callee = new Callee(this);


}


void doOwnTask(){


    cout<<"Main thread id = " <<this_thread::get_id()<<endl;


    for(int i = 0;i<10;i++){


cout<<"Caller is doing its own task in the main thread..."<<endl;


}


}


void delegateTaskToCallee(){


callee->doBackgroundTask();


}


virtual ~Caller(){


}


void onStartTask(){


cout<<"The background task is starting"<<endl;


}


void onFinishTask(){

cout<<"The background task is finished. Thank you, Callee, for taking my burden"<<endl;


}


};


#endif /* CALLER_H_ */




/*

* Callee.h

*

* Created on: Feb 15, 2024

* Author: som

*/


#ifndef CALLEE_H_

#define CALLEE_H_


#include <iostream>

#include <future>

#include "CallBack.h"


using namespace std;


class Callee {


private:

CallBack* cb;


public:


Callee(CallBack* cb){


this->cb = cb;


}


virtual ~Callee(){


}


static bool task(){


cout<<"Background thread id = " <<this_thread::get_id()<<endl;


for(int i = 0;i<10;i++){

    cout<<"Callee is doing the background task"<<endl;

}


return true;


}


void doBackgroundTask(){


this->cb->onStartTask();


future<bool> futureTask = async(launch::async, Callee::task);


if(futureTask.get() == true){

    this->cb->onFinishTask();

    }


}


};


#endif /* CALLEE_H_ */




//============================================================================

// Name : Callback.cpp

// Author : Som

// Version :

// Copyright : som-itsolutions

// Description : Hello World in C++, Ansi-style

//============================================================================


#include <iostream>


#include "CallBack.h"


#include "Caller.h"


#include "Callee.h"


using namespace std;


int main() {


Caller* caller = new Caller();


caller->doOwnTask();


caller->delegateTaskToCallee();



return 0;


}



You may like my other research study vis-a-vis how the

evenlistener pattern has been implemented in Android UI input

events.


Here we go...


Tuesday, February 13, 2024

Are we heading for a doomsday - HAARP - the earthquake weapon of USA...

 HAARP, which stands for High-frequency Active Auroral Research Program, is a research facility located in Gakona, Alaska. Its primary purpose is to study the ionosphere, the uppermost layer of Earth's atmosphere. The ionosphere is ionized by radiation from the Sun, and it plays an important role in radio communications and navigation.


HAARP uses a powerful radio transmitter to send beams of radio waves into the ionosphere. These beams can cause changes in the ionosphere, which can be studied by other instruments at the facility. This research helps scientists to understand how the ionosphere works and how it can be affected by natural and man-made phenomena.

Now the crux...

Conspiracy theories !!! Universe you decide...



Here's another video - technical



READ... READ...



Sunday, February 4, 2024

jthread in C++ 20 concurrency - a wrapper around std::thread...

jthread is a new entrant in C++ 20. It's a simple wrapper around std::thread which is based upon RAII objective - that is resource acquisition is resource initialization.

The j in the jthread stands for automatic joining.

This means that there is no need to call join on the newly created thread object - the program won't terminate abnormally.

When a jthread object goes out of scope or is otherwise destroyed, it automatically calls join() to ensure the thread completes before the object is destroyed. This prevents potential resource leaks or undefined behavior that could occur with std::thread.

The other aspect of jthread is the cooperative interruption.

We will discuss it in the next post.

Saturday, January 20, 2024

The Barrier in C++ 20 concurrency - the programmer in me is still thriving...

Enjoy my training video on the C++ barrier...



Suppose three workers prepare data at different speeds, but a computation must begin only after all workers are ready. How do we synchronize them?

std::barrier is the answer in C++.

The std::barrier class is a synchronization primitive introduced in C++20. It allows a set of threads to synchronize at a certain point in their execution. It is similar to the std::latch class, but it can be reused multiple times.

A std::barrier object is initialized with a count, which specifies the number of threads that must reach the barrier before any of them can proceed. When a thread reaches the barrier, it calls the wait() method. If the count is not yet zero, the thread will be blocked until the count reaches zero. Once the count reaches zero, all of the threads that are waiting on the barrier will be released and can proceed.

The std::barrier class can be used to implement a variety of synchronization patterns, such as producer-consumer queues, parallel algorithms, and race condition prevention.

Here's my application in which I used barrier to showcase how it can be used.

Class Student

#include <iostream>

#include <string>

#include <thread>

#include <barrier>

#include <chrono>

#include <vector>

#include <syncstream> // For thread-safe console output

#include <format> // For C++20 string formatting


class Student {

private:

std::string name;

int timeLapseBeforeStarting; // in milliseconds

int timeToFinish; // in milliseconds


public:

// Optimized constructor using std::move to avoid redundant copies

Student(std::string studentName, int lapse, int finish)

: name(std::move(studentName)),

timeLapseBeforeStarting(lapse),

timeToFinish(finish) {}


// Virtual destructor is fine if subclassing, but defaulted here

virtual ~Student() = default;


void task(std::barrier<>& b) {

// 1. Simulate varying arrival times

std::this_thread::sleep_for(std::chrono::milliseconds(timeLapseBeforeStarting));


// 2. Synchronize at the barrier

b.arrive_and_wait();


auto now = std::chrono::system_clock::now();


// Locate the current system timezone and convert UTC to local time

auto local_time = std::chrono::current_zone()->to_local(now);


// std::osyncstream guarantees this block of output won't interleave with other threads

std::osyncstream(std::cout)

<< name << " is Starting the task at "

<< std::format("{:%F %T}", local_time) << "\n";

// 4. Simulate performing the task

std::this_thread::sleep_for(std::chrono::milliseconds(timeToFinish));


std::osyncstream(std::cout) << name << " finished the task.\n";

}

};

// ==========================================

// Class classETC

// ==========================================

#include "Student.h"

class classETC {

public:

Student ridit {"Ridit", 1000, 2000};

Student ishan {"Ishan", 3000, 1000};

Student rajdeep {"Rajdeep", 900, 1500};


classETC() = default;

virtual ~classETC() = default;


// FIX: Returns a container of jthreads to main() to keep them alive

auto giveTaskToStudent(std::barrier<>& b) {

std::vector<std::jthread> workers;

workers.reserve(3);


// Emplace threads directly into the vector

workers.emplace_back(&Student::task, &this->ridit, std::ref(b));

workers.emplace_back(&Student::task, &this->ishan, std::ref(b));

workers.emplace_back(&Student::task, &this->rajdeep, std::ref(b));


return workers; // Named Return Value Optimization (NRVO) handles this cleanly

}

};

The Main method

// ==========================================

// Main Method

// ==========================================

#include "classETC.h"

int main() {

// Print C++ standard validation

std::cout << "C++ Version: ";

if (__cplusplus == 202101L) std::cout << "C++23\n";

else if (__cplusplus == 202002L) std::cout << "C++20\n";

else std::cout << "Other/Experimental (" << __cplusplus << ")\n";

std::cout << "-------------------------------------------\n";


std::barrier b(3);

classETC etc;


// FIX: Capturing the threads in main() preserves their lifetimes.

// They will execute concurrently and automatically join when main() ends.

auto threads = etc.giveTaskToStudent(b);


return 0;

}

The heart of the above concurrent program is

 b.arrive_and_wait();
  • arrive() decrements the participant count.

  • wait() blocks until the count reaches zero.

  • arrive_and_wait() combines both.

The Output:

C++ Version: C++20

-------------------------------------------

Ridit is Starting the task at 2026-06-26 00:19:59.803930207

Ishan is Starting the task at 2026-06-26 00:19:59.803930524

Rajdeep is Starting the task at 2026-06-26 00:19:59.803929510

Ishan finished the task.

Rajdeep finished the task.

Ridit finished the task.

Have a look at the time when the three different threads are starting - all of them start at the same time - 00:19:59 - why?

It's because of the barrier!!!

And for the inquisitive mind...

Why is there a mismatch in the starting time in the range of just a few microseconds?

A variation of 1 to 20 microseconds is incredibly tight and proves that our std::barrier system is doing its job perfectly. In a multi-threaded system running on top of a general-purpose operating system (like Linux/Ubuntu), achieving true 0.000000 synchronicity is physically impossible due to these hardware and scheduling realities. To get any closer, we would need a hard Real-Time Operating System (RTOS) with core pinning and disabled interrupts!

Enjoy...