Sunday, April 14, 2024

FuturesOrdered: Keeping Concurrency in Order In Rust

The use case is as follows: You have some tasks you want to execute concurrently, but you want the results to be returned in the same order the tasks were defined to be executed.

As an example, let's say you have some records returned from the database, as specified by some query. But before returning the records, some transformation needs to be done, and these transformations can be done concurrently. Due to the nature of concurrency, the order in which the transformations will be done is nondeterministic, meaning the order of the transformed records can and will end up being different from the original order of the records as retrieved from the database. This is something you do not want; you want the transformed records to be in the same order as specified in the original query that retrieved them from the database.

This sounds like the perfect task for Rust's FuturesOrdered type from the futures crate.

Reading the documentation, it states:

"...it imposes a FIFO order on top of the set of futures. While futures in the set will race to completion in parallel, results will only be returned in the order their originating futures were added to the queue."

Just what we want.

Let us illustrate with some code.

The dependencies:
[dependencies]
futures = "0.3.30"
tokio = { version = "1.37.0", features = ["full"]}
rand = "0.8.5"
The code:
async fn transform(number: i32) -> i32 {
    let mut rng = rand::thread_rng();
    let random_number = rng.gen_range(1..=5);
    tokio::time::sleep(Duration::from_secs(random_number)).await;
    println!("Done from {number}");
    return number;
}

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut tasks = FuturesOrdered::new();
    for n in 0..9 {
        tasks.push_back(async move { transform(n).await })
    }
    let mut results = vec![];
    while let Some(result) = tasks.next().await {
        results.push(result)
    }
    println!("{results:?}");
    Ok(())
}

The transform function represents the concurrent task. It sleeps for a random amount of seconds between 1 and 5, prints it is done, and then returns the transformed value, which in this case is just the original value passed to the function.

Running the code above should give something like:

Done from 1
Done from 3
Done from 6
Done from 9
Done from 5
Done from 7
Done from 0
Done from 2
Done from 4
Done from 8
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

The Done from n shows that the tasks are finished in different ordering, but the final printing of the results is still in order, showing that even though the tasks get executed concurrently and finishes out of order, when the result is retrieved via calling `tasks.next().await` it is returned in other.

Another way is to collect the results into a vec. That is:
#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut tasks = FuturesOrdered::new();
    for n in 0..=9 {
        tasks.push_back(transform(n))
    }
    let results = tasks.collect::<Vec<i32>>().await;
    println!("{results:?}");
    Ok(())
}

With this, the tasks would also execute, concurrently, finish in nondeterministic order, but the results will be collected in order as the tasks were started.


Friday, May 05, 2023

Fixing "Failed to get Recent Blockhash" Error in Solana Development with Anchor Framework

I took a break from exploring development in Solana, and the first thing I hit when I recently resumed again was failing tests when using the Anchor framework.

Running anchor run test leads to this error:

  1) calculator
  Is initialized!:
  Error: failed to get recent blockhash: FetchError:
  request to http://localhost:8899/ failed, reason:
  connect ECONNREFUSED ::1:8899
  at Connection.getLatestBlockhash
  (node_modules/@solana/web3.js/src/connection.ts:4555:13)
  at processTicksAndRejections
  (node:internal/process/task_queues:95:5)
  at AnchorProvider.sendAndConfirm
  (node_modules/@coral-xyz/anchor/src/provider.ts:147:9)
  at MethodsBuilder.rpc [as _rpcFn]
  (node_modules/@coral-xyz/anchor/src/program/namespace/rpc.ts:29:16)

The important part of the error message is:

Error: failed to get recent blockhash: FetchError:
request to http://localhost:8899/ failed, reason:
connect ECONNREFUSED ::1:8899
      at Connection.getLatestBlockhash

Googling the error, I found this suggestion here which says to "Try using node 16 instead of 17 if you're on 17".

I tried this and sure this works. But it does not feel right. node's current version is 20.x, dropping down to version 16 does not feel like the most appropriate way to solve this problem.

So why is the issue happening in the first place? Maybe if we understand that, we can come up with a better solution.


Monday, January 23, 2023

A Short Note on Types in Rust

Types, loosely defined, are about capabilities. What are the capabilities available on a value of a certain type? Programmers experienced with strong and statically typed languages will probably have a more visceral appreciation of this, but even programmers of weakly and dynamically typed languages like JavaScript, encounter the reality of types and their capabilities; only that they do so at runtime.

For example, a JavaScript developer might type out the following code:

> "3".toExponential()

But this will fail at runtime because the "3" is of the String type and it does not have the capability to be shown as exponential. The following, on the other hand, will work seamlessly:

> (3).toExponential()
'3e+0'

Because 3 is of the number type and the number type has the capability to be formatted in exponential form.

Rust extends the idea of types to now include capabilities that relate to memory management. These capabilities ensure memory safety in Rust.


Tuesday, October 11, 2022

Introduction to Declarative Macros in Rust

This post will give a quick overview of declarative macros in Rust. If you are not familiar with the concepts of macros in Rust, then read Introduction to Macros in Rust first.

Declarative macros work similar to how pattern matching works. It involves taking inputs, then pattern matching to determine what logic to execute.

The macros definition for creating declarative macros, then contains rules which determine the Rust code that gets generated/executed based on the pattern.

This is why declarative macros are also sometimes referred to as Macros By Example, as the code that is generated depends on the example of patterns that matches.


Monday, October 10, 2022

Introduction to Macros In Rust

This guide will present an overview of the fundamentals a new developer approaching macros in Rust needs to know. If you have read other materials online and you still find the concept of macros in Rust vague, then this post is for you.

The guide will be broken into 4 series of posts. The first post, which is this post, will be a quick overview of macros in Rust. The following 3 posts will then look more deeply into the different types of macros in Rust and how to create them.

The posts in this series are:

  • Introduction to macros in Rust (this post)
  • Introduction to declarative macros in Rust
  • Introduction to attribute-like procedural macros in Rust (yet to be published)
  • Introduction to function-like procedural macros in Rust (yet to be published)
  • Introduction to custom-derive procedural macros in Rust (yet to be published)

What are macros

Macros are mechanisms for generating or manipulating code programmatically at compile time. In essence, it allows us to treat source code as values. Meaning that, at compile time, we can take inputs: where the input could be another source code or some defined string pattern, and from these inputs, manipulate and generate source codes that form the final compiled code that will be executed.

Macros as a concept are not specific to Rust, as they can be found in languages like Lisp, C/C++, Scala, and a host of others. Although one clear distinction of macros in Rust is that it provides type safety. Meaning that it provides mechanisms to prevent generating programs or source code that are not type-safe.

That is what macros are in a nutshell, a mechanism for taking inputs (other source codes, or just string patterns) and using these to generate source code/program at compile time.

So now that we know what macros are, we talk about how macro usage looks like.

What does macros usage look like in Rust?

Before looking at how to create macros in Rust, it is instructive to see how already created macros are used.

There are two distinct ways of making use of macros in Rust. macros are either used via A syntax I call bang function syntax or via attribute syntax.

The bang function syntax is the syntax that looks like a function call but with an exclamation mark at the end of the function name. For example things like: println!(), dbg!(), format!().

These are used to invoke macros and are not normal function calls.

Attributes syntax, on the other hand, is the syntax used for annotation. It takes the form of #[..] or #![..] and they can be placed on various language elements like functions, structs, enums, etc. This is another syntax that can be used to invoke macros.

A real-world example of attribute syntax being used to invoke macros can be found in the web framework called rocket. For example in the code below, gotten from the documentation here where we see the definition of a handler for the / HTTP path:

#[get("/")]
fn index() -> &'static str {
    "Hello, world!"
}

The #[get("/")] syntax is invoking a macro defined by the rocket framework. This invocation means that at compile time, the custom get macro will take the function index() as input, process it, and generate the exact source code, the framework needs to be able to call this function in response to an HTTP GET request to the path /. Essentially fulfilling what a macro is: a mechanism to generate source code at compile time.

The exact syntax used to call a macro depends on the type of macro and how it has been created. Some macro types are called via the bang function syntax, while other kinds are called via the attribute syntax.

Types of Rust macros.

In Rust, there are two types of macros: Declarative macros and Procedural macros. There are then 3 subcategories of procedural macros: Custom-derive macros, Attribute-like macros, and Function-like macros.

Basically types of Rust macros:

  • Declarative macros (also referred to as macro by example or mbe for short)
  • Procedural macros
    • Function-like
    • Attribute-like macros
    • Custom derive macros

The first thing to note in this categorisation is what makes declarative macros different from procedural Macros.

The difference lies in their inputs and outputs, and also how they are created and used.

Declarative macros take what can be described as token fragments while procedural macros take TokenStream as inputs. Token fragments are like string patterns that can further be qualified by a fragment specifier that tells what kind they are. Fragment specifiers can be used to specify the inputs are block, expression, statement, etc. See Fragment Specifiers for an overview of the supported fragment specifiers.

Procedural macros, on the other hand, take in TokenStream which is a more low-level representation of the source code.

The other difference between declarative macros and procedural macros (also the difference between the 3 kinds of procedural macros) is how they are created and used.

Declarative macros and function-like procedural macros are used by a syntax that looks like function invocation but ends with !. for example do_work!(). Meaning when looking at usage it is not possible to tell if a declarative macro is being used or a function-like procedural macro is.

Attribute-like procedural macros on the other hand are used to create a custom annotation, so they are used with the syntax of applying attributes. That is: #[my_macro]. They can also be defined to take helper attributes. That is #[my_macro(attr)]

While custom derives macros are used to define new values that can be passed into the #[derive(...)] macro. For example, if MyMacro is defined to be a custom derive macro, it can then be used as #[derive(MyMacro)]

This completes the quick overview of macros in Rust. The following posts in the series will then go into the details of how these different types of macros are created and used and their different peculiarities.


Wednesday, September 28, 2022

How to query account balance on Solana using Rust.

This post shows how to query Solana's cluster rpc endpoint using Rust. Most of the examples of how to perform this task that I ran to uses Javascript/Typescript via the Solana Web3js library, so I decided to do a quick post showing how to do same but in Rust. It uses querying for the sol balance of a Solana account as the main example of interaction with the rpc endpoint.

The task is simple: given a Solana address, we retrieve the sol balance for that address.

To get started we will need the following dependencies:

[dependencies]
solana-client = "1.14.3" // update to the current latest version
solana-sdk = "1.14.3" // update to the current latest version

The code snippet below then shows how to call the rpc endpoint and retrieve the Sol balance for an account

use solana_client::rpc_client::RpcClient;
use solana_program::pubkey::Pubkey;
use std::str::FromStr;

fn main() {
 let rpc = RpcClient::new("https://api.devnet.solana.com");
 let pubkey_str = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU";

 let balance = rpc
      .get_account(&Pubkey::from_str(pubkey_str).unwrap())
      .unwrap().lamports;

 println!("Sol balance of {pubkey_str} is {balance}");
}

Solana addresses are base58 encoded strings, hence they need to be first converted into an instance of Pubkey before they can be used in the rpc.get_account call, which expects a &[u8] byte array. To help with this conversion, Pubkey implements the std::str::FromStr trait. This wmeans, if the trait std::str::FromStr is in scope, then it is possible to convert a string to Pubkey, which is what was done in the code snippet above.

This post was inspired by a recent question I answered on Solana stack exchange here. The answer also mentions another verbose alternative that involves manually converting from base58 to bytes array.


Saturday, August 13, 2022

IntoIterator and the for ... in Syntax in Rust

In Rust Iterator pattern with iter(), into_iter() and iter_mut() methods I explained why attempting to use a variable holding a Vec after iterating through it using the for … in syntax leads to a compilation error.

The post explains why the following code won't compile:

fn main() {
   let some_ints = vec![1,2,3,4,5];
  // iterating through a vec
   for i in some_ints {
       dbg!(i);
   }
// attempting to use the vec will 
// lead to compile error after the iteration
   dbg!(some_ints);
}

I then showed 3 methods that can be called before iterating using the for … in and how 2 of these methods allow the Vec to still be used even after iteration.

These 3 methods are into_iter(), iter(), and iter_mut(). That is:

#[test]
fn into_iter_demo() {
    // the .into_iter() method creates an iterator, v1_iter 
    // which takes ownership of the values being iterated.
    let mut v1_iter = v1.into_iter();

    assert_eq!(v1_iter.next(), Some(1));
    assert_eq!(v1_iter.next(), Some(2));
    assert_eq!(v1_iter.next(), Some(3));
    assert_eq!(v1_iter.next(), None);

    // If the line below is uncommented, the code won't compile anymore
    // this is because, after the iteration, v1 can no longer be used 
    // since the iteration moved ownership
    //dbg!(v1);
}

The two other methods that allow the Vec to still be used after iteration via for … in are:

#[test]
fn iter_demo() {
    let v1 = vec![1, 2, 3];
    // the .iter() method creates an iterator, 
    // v1_iter which borrows value immutably 
    let mut v1_iter = v1.iter();

    // iter() returns an iterator of slices.
    assert_eq!(v1_iter.next(), Some(&1));
    assert_eq!(v1_iter.next(), Some(&2));
    assert_eq!(v1_iter.next(), Some(&3));
    assert_eq!(v1_iter.next(), None);
   // because values were borrowed immutably, 
   // it is still possible to use 
   // the vec after iteration is done
    dbg!(v1);
}

And

#[test]
fn iter_mut_demo() {
    let mut v1 = vec![1, 2, 3];

    // the .iter_mut() method creates an iterator, 
    // v1_iter which borrows value and can mutate it. 
    let mut v1_iter = v1.iter_mut();

    // access the first item and multiple it by 2
    let item1 = v1_iter.next().unwrap();
    *item1 = *item1 * 2;

    // access the second item and multiple it by 2
    let item2 = v1_iter.next().unwrap();
    *item2 = *item2 * 2;

    // access the third item and multiple it by 2
    let item3 = v1_iter.next().unwrap();
    *item3 = *item3 * 2;

    // end of the iteration
    assert_eq!(v1_iter.next(), None);

    // this will print out [2,4,6]
    dbg!(v1);
}

In this post, we are going to dive a little bit deeper into understanding some of the machinery that makes the above work.

We start again by talking about the Iterator trait.