Reader small image

You're reading from  Asynchronous Programming in Rust

Product typeBook
Published inFeb 2024
PublisherPackt
ISBN-139781805128137
Edition1st Edition
Right arrow
Author (1)
Carl Fredrik Samson
Carl Fredrik Samson
author image
Carl Fredrik Samson

Carl Fredrik Samson is a popular technology writer and has been active in the Rust community since 2018. He has an MSc in Business Administration where he specialized in strategy and finance. When not writing, he's a father of two children and a CEO of a company with 300 employees. He's been interested in different kinds of technologies his whole life and his programming experience ranges from programming against old IBM mainframes to modern cloud computing, using everything from assembly to Visual Basic for Applications. He has contributed to several open source projects including the official documentation for asynchronous Rust.
Read more about Carl Fredrik Samson

Right arrow

Creating Our Own Fibers

In this chapter, we take a deep dive into a very popular way of handling concurrency. There is no better way of getting a fundamental understanding of the subject than doing it yourself. Fortunately, even though the topic is a little complex, we only need around 200 lines of code to get a fully working example in the end.

What makes the topic complex is that it requires quite a bit of fundamental understanding of how CPUs, operating systems, and assembly work. This complexity is also what makes this topic so interesting. If you explore and work through this example in detail, you will be rewarded with an eye-opening understanding of topics you might only have heard about or only have a rudimentary understanding of. You will also get the chance to get to know a few aspects of the Rust language that you haven’t seen before, expanding your knowledge of both Rust and programming in general.

We start off by introducing a little background knowledge that...

Technical requirements

To run the examples, you will need a computer running on a CPU using the x86-64 instruction set. Most popular desktop, server, and laptop CPUs out there today use this instruction set, as do most modern CPUs from Intel and AMD (which are most CPU models from these manufacturers produced in the last 10–15 years).

One caveat is that the modern M-series Macs use the ARM ISA (instruction set), which won’t be compatible with the examples we write here. However, older Intel-based Macs do, so you should be able to use a Mac to follow along if you don’t have the latest version.

If you don’t have a computer using this instruction set available, you have a few options to install Rust and run the examples:

  • Mac users on M-series chips can use Rosetta (which ships with newer MacOS versions) and get the examples working with just four simple steps. You’ll find the instructions in the repository under ch05/How-to-MacOS-M.md.
  • ...

How to use the repository alongside the book

The recommended way to read this chapter is to have the repository open alongside the book. In the repository, you’ll find three different folders that correspond to the examples we go through in this chapter:

  • ch05/a-stack swap
  • ch05/b-show-stack
  • ch05/c-fibers

In addition, you will get two more examples that I refer to in the book but that should be explored in the repository:

  • ch05/d-fibers-closure: This is an extended version of the first example that might inspire you to do more complex things yourself. The example tries to mimic the API used in the Rust standard library using std::thread::spawn.
  • ch05/e-fibers-windows: This is a version of the example that we go through in this book that works on both Unix-based systems and Windows. There is a quite detailed explanation in the README of the changes we make for the example work on Windows. I consider this recommended reading if you want to dive deeper...

Background information

We are going to interfere with and control the CPU directly. This is not very portable since there are many kinds of CPUs out there. While the overall implementation will be the same, there is a small but important part of the implementation that will be very specific to the CPU architecture we’re programming for. Another aspect that limits the portability of our code is that operating systems have different ABIs that we need to adhere to, and those same pieces of code will have to change based on the different ABIs. Let’s explain exactly what we mean here before we go further so we know we’re on the same page.

Instruction sets, hardware architectures, and ABIs

Okay, before we start, we need to know the differences between an application binary interface (ABI), a CPU architecture, and an instruction set architecture (ISA). We need this to write our own stack and make the CPU jump over to it. Fortunately, while this might sound complex...

An example we can build upon

This is a short example where we will create our own stack and make our CPU return out of its current execution context and over to the stack we just created. We will build on these concepts in the following chapters.

Setting up our project

First, let’s start a new project by creating a folder named a-stack-swap. Enter the new folder and run the following:

cargo init

Tip

You can also navigate to the folder called ch05/a-stack-swap in the accompanying repository and see the whole example there.

In our main.rs, we start by importing the asm! macro:

ch05/a-stack-swap/src/main.rs

use core::arch::asm;

Let’s set a small stack size of only 48 bytes here so that we can print the stack and look at it before we switch contexts after we get the first example to work:

const SSIZE: isize = 48;

Note

There seems to be an issue in macOS using such a small stack. The minimum for this code to run is a stack size of 624 bytes...

The stack

A stack is nothing more than a piece of contiguous memory.

This is important to know. A computer only has memory, it doesn’t have a special stack memory and a heap memory; it’s all part of the same memory.

The difference is how this memory is accessed and used. The stack supports simple push/pop instructions on a contiguous part of memory, that’s what makes it fast to use. The heap memory is allocated by a memory allocator on demand and can be scattered around in different locations.

We’ll not go through the differences between the stack and the heap here since there are numerous articles explaining them in detail, including a chapter in The Rust Programming Language at https://doc.rust-lang.org/stable/book/ch04-01-what-is-ownership.html#the-stack-and-the-heap.

What does the stack look like?

Let’s start with a simplified view of the stack. A 64-bit CPU will read 8 bytes at a time. Even though the natural way for us to see...

Implementing our own fibers

Before we start, I want to make sure you understand that the code we write is quite unsafe and is not a “best practice” when writing Rust. I want to try to make this as safe as possible without introducing a lot of unnecessary complexity, but there is no way to avoid the fact that there will be a lot of unsafe code in this example. We will also prioritize focusing on how this works and explain it as simply as possible, which will be enough of a challenge in and of itself, so the focus on best practices and safety will have to take the back seat on this one.

Let’s start off by creating a whole new project called c-fibers and removing the code in main.rs so we start with a blank sheet.

Note

You will also find this example in the repository under the ch05/c-fibers folder. This example, as well as ch05/d-fibers-closure and ch05/e-fibers-windows, needs to be compiled using the nightly compiler since we use an unstable feature. You...

Finishing thoughts

I want to round off this chapter by pointing out some of the advantages and disadvantages of this approach, which we went through in Chapter 2, since we now have first-hand experience with this topic.

First of all, the example we implemented here is an example of what we called a stackful coroutine. Each coroutine (or thread, as we call it in the example implementation) has its own stack. This also means that we can interrupt and resume execution at any point in time. It doesn’t matter if we’re in the middle of a stack frame (in the middle of executing a function); we can simply tell the CPU to save the state we need to the stack, return to a different stack and restore the state it needs there, and resume as if nothing has happened.

You can also see that we have to manage our stacks in some way. In our example, we just create a static stack (much like the OS does when we ask it for a thread, but smaller), but for this to be more efficient than...

Summary

First of all, congratulations! You have now implemented a super simple but working example of fibers. You’ve set up your own stack and learned about ISAs, ABIs, calling conventions, and inline assembly in Rust.

It was quite the ride we had to take, but if you came this far and read through everything, you should give yourself a big pat on the back. This is not for the faint of heart, but you pulled through.

This example (and chapter) might take a little time to fully digest, but there is no rush for that. You can always go back to this example and read the code again to fully understand it. I really do recommend that you play around with the code yourself and get to know it. Change the scheduling algorithm around, add more context to the threads you create, and use your imagination.

You will probably experience that debugging problems in low-level code like this can be pretty hard, but that’s part of the learning process and you can always revert back...

lock icon
The rest of the chapter is locked
You have been reading a chapter from
Asynchronous Programming in Rust
Published in: Feb 2024Publisher: PacktISBN-13: 9781805128137
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime

Author (1)

author image
Carl Fredrik Samson

Carl Fredrik Samson is a popular technology writer and has been active in the Rust community since 2018. He has an MSc in Business Administration where he specialized in strategy and finance. When not writing, he's a father of two children and a CEO of a company with 300 employees. He's been interested in different kinds of technologies his whole life and his programming experience ranges from programming against old IBM mainframes to modern cloud computing, using everything from assembly to Visual Basic for Applications. He has contributed to several open source projects including the official documentation for asynchronous Rust.
Read more about Carl Fredrik Samson