Rust’s Future: Internal Execution

Reading Time: 4 minutes

As you all know Rust Programming has Futures which helps to make our code Asynchronous. Rust's Futures are analogous to Promises.
This article mainly pertains to the internal working of the Future and we’ll also understand the structure of the Future like how it is defined and how it will execute when it comes into the action.

Future: In a Nutshell

Future is trait in Rust Programming Language and it represents an asynchronous computation. Futures allow you to write a code that can be run asynchronously.
Rust’s Futures are always Results that means you have to specify both the expected return type and the alternative error one.
With the help of this, we can chain functions onto the result, then we can transform it, handle errors, can join with other futures as well.

It is a concept for an object which is a proxy for another value that may not be ready yet.

Future: When to use

Let’s consider a scenario, where you have multiple jobs in your program. And as you already know that Rust is a synchronous language, so it waits until the first job to complete then will it execute the next job. So this is the scenario where you can use Futures and make your program asynchronous. By telling the compiler like “Hey there are multiple jobs to execute, so instead of waiting for the first job to complete let’s jump to the other jobs asynchronously“.
There are many reasons why we might want to use a future instead of a standard function: performance, elegance, composability, and so on.

I guess, now we all are familiar with Rust’s Futures, and when to use Futures.
Now, let’s understand the internal execution of Futures.

Future: Internals

Let’s create simple Future.

trait Future {
    type Output;
    fn poll(&mut self, wake: fn()) -> Poll<Self::Output>;
}

enum Poll<T> {
    Ready(T),
    Pending,
}

As this is the simple signature of the Future trait, however, the actual Rust’s Future has a slightly different signature or you can say, that Future has some advanced approach that helps in the scenario of immovability and managing all the contexts separately.

Let’s understand each point one by one:
We have a trait called the Future which contains type and function.

  • type Output: it is self-explanatory that Future trait has a type called as Output.
  • fn poll(&mut self, wake: fn()) -> Poll<Self::Output>:
    a simple function has two parameters and which returns an enum.
  • Poll<T>: an enum with Ready<T> and Pending.

This poll function is a major part of the future. It actually starts the flow of asynchronous execution, like if the Future completes, it returns Poll::Ready(T). If the future is not able to complete yet, it returns Poll::Pending and arranges for the wake() function to be called when the Future is ready to make more progress.

And wake function also plays an important role in the Future.
Wake tells the executor which futures are ready to be polled and without it the executor would not be able to get the information about which Future could make progress and then executor would have to poll all the Futures constantly.

Hope you all get what actually happens under the hood in the Futures. Now let’s understand this execution more clearly by implementing a small piece of code.

// This is our structure with one member `count`. 
struct SimpleData {
    count: u32,
}
// Let's implement the Future for this SimpleData 
impl Future for SimpleData {
    type Output = u32;

    fn poll(&mut self, wake: fn()) -> Poll<Self::Output> {
        if self.count == 10 {
            Poll::Ready(self.count)
        } else {
            // The count does not reach it's desired value yet which is 10.
            // Let's increment the value by one.
            self.count() += 1;
            // set the `wake` function as a callback as our Future is not resolved yet.
            // Rust's Future uses Context in place of function pointer (fn()) which provides the type waker.
            // Waker internally implements `clone()` so that it can be copied.
            // It helps the executor to woke up the task for the current Future.
            wake; 
            Poll::Pending
        }
    }
}

In the about example we just implemented the internal functionality of the Future. It won’t compile, it is just to understand the internals of the Future. The major role is driven by poll() method which actually returns the resolved Future by returning the Poll::Ready or invokes the wake by returning the Poll::Pending.
Now let’s understand the code:

  • Firstly we have created a structure with a counter.
  • Then we have implemented the Future trait for our structure.
  • In the poll()‘s implementation, we have provided the condition.
  • If count equals 10 then we are returning a Poll::Ready response with the value, 10.
  • And if the count does not equals to 10 then we are incrementing the count and returning Poll::Pending

Here wake plays an important role as the official Rust’s Future uses the Content in place of function pointer (fn()). The Context provides the type Waker.

Future: Waker

Waker provides a wake() method that can be used to tell the executor that the associated task should be awoken. When wake() is called, the executor knows that the task associated with the Waker is ready to make progress, and its future should be polled again.

Hope you got some insights for the internal working of the Asynchronous Program.
Thanks for reading!!!

If you want to read more content like this?  Subscribe Rust Times Newsletter and receive insights and latest updates, bi-weekly, straight into your inbox. Subscribe Rust Times Newsletter: https://bit.ly/2Vdlld7 .

Video: Learn more and enhance your knowledge and skills of Rust Language by subscribing Rust Times newsletter and receive updates bi-weekly. https://bit.ly/2Vdlld7 .

Template: For more such template updates, subscribe Rust Times Newsletter: https://bit.ly/2Vdlld7


Knoldus-blog-footer-image

Written by 

Pawan Singh Bisht is a Software Consultant at Knoldus Software LLP, having a strong experience of more than two years in the technology field. He has been well versed in the core implementation of Rust and Java. He loves to contribute to the community which he attained from the community.

Discover more from Knoldus Blogs

Subscribe now to keep reading and get access to the full archive.

Continue reading